Задача. Построить метод для передвижения кнопки в указанные координаты. Передвижение кнопки должно осуществляться анимацией, т.е. плавно от исходной позиции в конечную. Построить анимацию для группы кнопок, запускаемую по щелчку в окне приложения. Скорость движения элементов должна быть постоянной, независимо от расстояния перемещения.
Для перемещения кнопки (или любого другого элемента) по плоской форме достаточно двух координат Left по горизонтали и Top по вертикали. В силу философии компоновки WPF позиционированием своих детей ведают контейнеры и поэтому элементы не имеют свойств Left и Top. Класс Button, соответственно, также не имеет свойств абсолютного позиционирования.
Чтобы кнопки перемещались точно в указанные координаты, в качестве контейнера, выберем панель Canvas. Canvas автоматически не позиционирует свои дочерние элементы, но позволяет указывать для них абсолютные места размещения. Статический методы Canvas.SetLeft(элемент, координата-X), Canvas.SetTop(элемент, координата-Y) перемещают элементы в любую позицию в пределах канвы.
Метод анимационного перемещения кнопки создадим в отдельном классе. Таким образом, используя принцип инкапсуляции, значительно уменьшим количество кода в файле главного окна приложения.
Построим скелет класса, назовём его Moving:
Листинг скелета класса:
static class Moving
{
public static void MoveTo(FrameworkElement fe, double x, double y)
{
...
}
}
Поскольку в составе класса только один метод и нет ни одного поля, рациональней объявить его статическим. Метод движения элемента в качестве параметров принимает сам элемент и координаты пункта назначения. Хотя речь идет об анимации кнопок, в качестве первого параметра можно использовать элемент любого типа.
По условию нет необходимости создавать длительную анимацию, поэтому код упростим применением метода UIElement.BeginAnimation(…), который наследует класс Button. Один метод с параметрами для Left координаты, аналогичный метод для Top координаты. BeginAnimation(...) работает в отдельном потоке, не прерывая основной. Два метода запустятся почти одновременно и управляемый элемент поедет по прямой до точки с координатами x и y.
Чтобы скорость была постоянной, длительность анимации должна быть пропорциональна расстоянию перемещения. Перед началом движения необходимо вычислять длину гипотенузы (катеты – Left->Left1 и Top->Top1) и делить её на значение скорости. Так мы получаем время движения в пути, обеспечивая константную скорость на любые дистанции.
Вот что у нас получилось, полный листинг класса Moving:
static class Moving
{
public static void MoveTo(FrameworkElement fe, double x, double y)
{
// Значение скорость условные единицы в секунду.
double speed = 300;
// Получение начальных координат
double leftInit = Canvas.GetLeft(fe);
double topInit = Canvas.GetTop(fe);
// Вычисление катетов. Нас интересует только расстояние,
// поэтому используем модули значений катетов.
double X = Math.Abs(x - leftInit);
double Y = Math.Abs(y - topInit);
double quart = Math.Sqrt(X * X + Y * Y);
// Время для данного расстояния с указанной скоростью.
double time = quart / speed;
var left = new DoubleAnimation
{
From = leftInit,
To = x,
Duration = new Duration(TimeSpan.FromSeconds(time))
};
var top = new DoubleAnimation
{
From = topInit,
To = y,
Duration = new Duration(TimeSpan.FromSeconds(time))
};
fe.BeginAnimation(Canvas.LeftProperty, left);
fe.BeginAnimation(Canvas.TopProperty, top);
}
}
Задача. Создать анимацию элемента напоминающего движение червяка. В качестве контейнера для элемента нельзя использовать панель Canvas. Движение построить на изменениях относительных величин. Обеспечить настройку частоты кадров анимации.
Благодаря тому, что все задачи выполнены в отдельных классах, программный код главного окна приложения получился небольшим и понятным.
Стоит отдельно описать создание группы кнопок. Множество элементов не рационально определять в XAML файле. Гораздо быстрее создать массив элементов класса Button с необходимыми настройками свойств и координатами размещения. Инициализация группы кнопок и объекта класса движения червяка происходят в конструкторе.
Код инициализации приложения:
private readonly WormAnimation wormAnimation;
readonly Button[] buttons = new Button[9];
public MainWindow()
{
InitializeComponent();
for(int i = 0; i < buttons.Length; i++)
{
buttons[i] = new Button();
buttons[i].Width = buttons[i].Height = 24;
buttons[i].Content = i + 1;
// Автоматический расчёт координат размещения.
Canvas.SetLeft(buttons[i], 0);
Canvas.SetTop(buttons[i], 24 * i);
// Каждая кнопка будет на поле Canvas.
fieldMoving.Children.Add(buttons[i]);
}
// Контейнер - Grid. Червяка представит Border.
// Скорость анимации 60 кадров в секунду обеспечит плавные движения.
wormAnimation = new WormAnimation(gridAquarium, earthworm, 60);
}
Код запуска анимаций группы кнопок и имитации движения червяка:
bool horizontal = true;
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < buttons.Length; i++)
{
double temp = (buttons[i].Width + 1) * i;
// Смена направления движения при каждом щелчке мыши.
if (horizontal == true) Moving.MoveTo(buttons[i], temp, 0);
else Moving.MoveTo(buttons[i], 0, temp);
}
// Флаг направления изменяем на противоположный.
horizontal = !horizontal;
}
private void gridAquarium_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
wormAnimation.LetsGo();
}
Исходный код написан в MS Visual Studio 2019. Два вида анимаций размещены в закладках контейнера TabControl. Исходник приложения комбинирует инициализацию элементов в XAML и в программном коде. Для работы с исходным кодом рекомендуется изучить данную статью.