Страницы

Поиск по вопросам

четверг, 11 октября 2018 г.

Как по координатам найти объект на Canvas'е и переместить его?

На интерфейсе располагается в центре кнопка, вокруг него элементы типа Image. С виду это выглядит как квадрат 3 на 3. При клике на кнопку окружающие картинки должны сместится на одну клетку при каждом клике. Например, картинка которая находится (объясняю на языке двухмерного массива), которая располагается на позиции 1 0 от кнопки, должна сместится на позицию 0 0, а другая картинка с позиции 0 0, на позицию 0 1.
При помощи такого подхода:
image10.BeginAnimation(MarginProperty, new ThicknessAnimation { From = new Thickness(99, 66, 361, 212), To = new Thickness(99, 24, 361, 254), Duration = TimeSpan.FromSeconds(0.85) });
мне удается выполнить такое действие до второго клика. После второго клика картинки возвращаются в начальные положения и повторяют действие первого клика, а нужно чтобы при каждом клике ничего не сбивалось и картинки перемещались по кругу. Чтобы реализовать такой подход, я понял, что строго к элементам обращаться не получится, ибо выходит то, что написал выше. Поэтому вопрос таков, как при помощи координат в коде C# мне обратится к объекту, который располагается в данном месте и анимированно переместить в другое место? Как я прочитал в интернете для поиска и перемещения нужно, чтобы все объекты располагались на канвасе. Однако скажу сразу, что я пытался при помощи такого подхода получить координаты.
Canvas.SetLeft(image10, 0); Canvas.SetTop(image10, 0);
Но при помощи такого подхода не выходит прицепить анимацию перемещения, а она очень важна.
P.S. Буду признателен, если озвучите более просто подход выполнения этой задумки.


Ответ

Давайте напишем класс, представляющий "плитку":
class Tile : Vm { int x; public int X { get => x; set => Set(ref x, value); }
int y; public int Y { get => y; set => Set(ref y, value); }
string caption; public string Caption { get => caption; set => Set(ref caption, value); } }
Вы в своем классе будете хранить картинку или ссылку на нее, я использую просто строковую надпись string Caption
Теперь главная ViewModel
class MainVM : Vm { ObservableCollection tiles; public ObservableCollection Tiles { get => tiles; set => Set(ref tiles, value); }
RelayCommand turnCommand; public ICommand TurnCommand => turnCommand;
public MainVM() { Tiles = new ObservableCollection { new Tile { Caption="1", X = 0, Y = 0 }, new Tile { Caption="2", X = 1, Y = 0 }, new Tile { Caption="3", X = 2, Y = 0 }, new Tile { Caption="4", X = 2, Y = 1 }, new Tile { Caption="5", X = 2, Y = 2 }, new Tile { Caption="6", X = 1, Y = 2 }, new Tile { Caption="7", X = 0, Y = 2 }, new Tile { Caption="8", X = 0, Y = 1 }, };
turnCommand = new RelayCommand(_ => TurnTiles()); }
void TurnTiles() { // Здесь используются фичи C# 7.0 и .NET Framework 4.7 Dictionary<(int, int), (int, int)> transitions = new Dictionary<(int, int), (int, int)> { [(0, 0)] = (0, 1), [(1, 0)] = (0, 0), [(2, 0)] = (1, 0), [(2, 1)] = (2, 0), [(2, 2)] = (2, 1), [(1, 2)] = (2, 2), [(0, 2)] = (1, 2), [(0, 1)] = (0, 2), }; foreach (var tile in Tiles) (tile.X, tile.Y) = transitions[(tile.X, tile.Y)]; /* Вариант "по-старинке", для тех, у кого по каким-то причинам не работает вариант выше Dictionary, Tuple> transitions = new Dictionary, Tuple> { [Tuple.Create(0, 0)] = Tuple.Create(0, 1), [Tuple.Create(1, 0)] = Tuple.Create(0, 0), [Tuple.Create(2, 0)] = Tuple.Create(1, 0), [Tuple.Create(2, 1)] = Tuple.Create(2, 0), [Tuple.Create(2, 2)] = Tuple.Create(2, 1), [Tuple.Create(1, 2)] = Tuple.Create(2, 2), [Tuple.Create(0, 2)] = Tuple.Create(1, 2), [Tuple.Create(0, 1)] = Tuple.Create(0, 2), }; foreach (var tile in Tiles) { var coords = transitions[Tuple.Create(tile.X, tile.Y)]; tile.X = coords.Item1; tile.Y = coords.Item2; } */ } }
В общем-то все просто, коллекция с "плитками" и команда для их перемещения по кругу. Причем в этом примере использовать ObservableCollection даже не обязательно, вы можете использовать обычный List transitions - это словарь переходов, ключ словаря - начальные координаты плитки, значение - конечные
Теперь займемся представлением. Напишем конвертеры координат VM => View
class XToLeftConverter : IValueConverter { public double TileWidth { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (int)value * TileWidth; }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
И
class YToTopConverter : IValueConverter { public double TileHeight { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (int)value * TileHeight; }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
В них определено публичное свойство, которое будет устанавливаться из разметки.
Теперь сама разметка окна, в качестве примера я использовал этот ответ