3

I want to show the animation of my moving ant on the canvas. Therefore the position of an ellipse should be changed. The calculation of the steps works, but I can't show the change of the ellipse position in the MainWindow. Only after the calculation of all the ant steps is done, the canvas is shown.

XAML-code:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Canvas Name="myCanvas">
        <Ellipse x:Name="ant1" Width="11" Height="11" Stroke="Black" Fill="Red"/>
    </Canvas>
</Window>

C#-code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
using System.Diagnostics;
using System.Windows.Threading;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();




            var random = new Random();

            var iterations = 10000;

            var numberOfAnts = 10;
            var ants = CreateAntCollection(numberOfAnts);

            // time-loop
            for (var iteration = 0; iteration < iterations; iteration++)
            {

                // move ants
                foreach (var ant in ants)
                {
                    var x = (random.Next(3) - 1) + ant.Position.X;
                    var y = (random.Next(3) - 1) + ant.Position.Y;

                    ant.Move(x, y);
                }

                // animate the ant
                // test - todo
                Debug.WriteLine(ants[0].Position.X);
                Canvas.SetLeft(ant1, ants[0].Position.X);   // movement not shown
            }
        }

        private static List<Ant> CreateAntCollection(int count)
        {
            var ants = new List<Ant>(count);

            for (var i = 0; i < count; i++)
            {
                var name = string.Format("ant-{0}", i);

                var ant = new Ant(name);

                ants.Add(ant);
            }

            return ants;
        }

    }


    class Ant
    {
        public Ant(string name)
        {
            Name = name;
            Position = new Position(80, 80);
        }

        public string Name { get; private set; }

        public Position Position { get; private set; }

        public void Move(int x, int y)
        {
            Position = new Position(x, y);
        }

        public override string ToString()
        {
            return Name;
        }
    }


    struct Position
    {
        public readonly int X;

        public readonly int Y;

        public Position(int x, int y)
        {
            X = x;
            Y = y;
        }

        public override string ToString()
        {
            return string.Format("{0},{1}", X, Y);
        }
    }
}

This "solution" doesn't work: element.InvalidateVisual();

kame
  • 20,848
  • 33
  • 104
  • 159

2 Answers2

3

The problem is that you are running the movement synchronously. You need to execute it in another thread. Something like this:

Task.Run(() => {
    for (var iteration = 0; iteration < iterations; iteration++)
    {
        // move ants
        foreach (var ant in ants)
        {
            var x = (random.Next(3) - 1) + ant.Position.X;
            var y = (random.Next(3) - 1) + ant.Position.Y;
            ant.Move(x, y);
        }

        // animate the ant
        Debug.WriteLine(ants[0].Position.X);
        this.Dispatcher.Invoke((Action)(() =>
        {
            Canvas.SetLeft(ant1, ants[0].Position.X);
        }));
    }
});
kame
  • 20,848
  • 33
  • 104
  • 159
Domysee
  • 12,718
  • 10
  • 53
  • 84
1

From where are you calling element.InvalidateVisual? More specifically, which thread? If you're running a simulation on the UI thread the canvas won't update until that is finished. What's usually recommended is to use the Dispatcher class by calling either Invoke or BeginInvoke.

As I said, I don't know where you're calling from, but a render might look like this:

private void Render()
{
    Dispatcher.Invoke((Action)(() =>
    {
        element.InvalidateVisual();
    }));
}

This seems to be another good (albeit old) question for updating the GUI from another thread.

Looking closer at your question, you should move your update code to another thread as well. Going through all iterations in the constructor as you do now is definitely blocking everything else. Basically everything after InitializeComponent() should be dealt with using a callback or a thread.

Community
  • 1
  • 1
Tobbe
  • 1,825
  • 3
  • 21
  • 37
  • I called `element.InvalidateVisual` behind `Canvas.SetLeft(ant1, ants[0].Position.X);` . Now I will create a new thread. – kame Oct 06 '15 at 06:39