RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 699252
Accepted
Prorok
Prorok
Asked:2020-07-29 17:35:35 +0000 UTC2020-07-29 17:35:35 +0000 UTC 2020-07-29 17:35:35 +0000 UTC

如何通过坐标在Canvas上找到一个物体并移动它?

  • 772

在界面上,中间有一个按钮,周围是Image类型的元素。它看起来像一个 3 x 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# 代码中使用坐标来引用位于给定位置的对象并将其动画化到另一个位置?正如我在 Internet 上阅读的那样,为了搜索和移动,所有对象都必须位于画布上。但是,我会马上说我尝试使用这种方法获取坐标。

Canvas.SetLeft(image10, 0);
Canvas.SetTop(image10, 0);

但是使用这种方式,附加运动动画是行不通的,很重要。

PS 如果您提出一种更简单的方法来实现这个想法,我将不胜感激。

c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    Андрей NOP
    2020-07-30T14:20:44Z2020-07-30T14:20:44Z

    让我们写一个代表“tile”的类:

    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<Tile> tiles;
        public ObservableCollection<Tile> Tiles
        {
            get => tiles;
            set => Set(ref tiles, value);
        }
    
        RelayCommand turnCommand;
        public ICommand TurnCommand => turnCommand;
    
        public MainVM()
        {
            Tiles = new ObservableCollection<Tile>
            {
                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<int, int>, Tuple<int, int>> transitions = new Dictionary<Tuple<int, int>, Tuple<int, int>>
            {
                [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();
        }
    }
    

    他们定义了一个将从标记中设置的公共属性。

    现在是窗口布局本身,我以这个答案为例:

    <Window x:Class="WpfTest.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:WpfTest"
            d:DataContext="{d:DesignInstance Type=local:MainVM}"
            mc:Ignorable="d" WindowStartupLocation="CenterScreen"
            Title="MainWindow" Height="400" Width="400">
    
        <Window.Resources>
            <local:XToLeftConverter x:Key="XToLeftConverter" TileWidth="100"/>
            <local:YToTopConverter x:Key="YToTopConverter" TileHeight="100"/>
        </Window.Resources>
    
        <Grid Margin="5"> 
            <ItemsControl ItemsSource="{Binding Tiles}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Border Height="99" Width="99"
                                BorderThickness="1" BorderBrush="Black">
                            <TextBlock Text="{Binding Caption}"
                                       VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </Border>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                <ItemsControl.ItemContainerStyle>
                    <Style>
                        <Setter Property="Canvas.Left"
                                Value="{Binding X, Converter={StaticResource XToLeftConverter}}"/>
                        <Setter Property="Canvas.Top"
                                Value="{Binding Y, Converter={StaticResource YToTopConverter}}"/>
                    </Style>
                </ItemsControl.ItemContainerStyle>
            </ItemsControl>
    
            <Button Content="Rotate" Command="{Binding TurnCommand}"
                    Width="99" Height="99" Margin="100,100,0,0"
                    VerticalAlignment="Top" HorizontalAlignment="Left"/>
        </Grid>
    </Window>
    

    有点歪,但到目前为止。
    原则上,这已经可行,但到目前为止,还没有动画。当你点击中间的按钮时,瓷砖会跳来跳去。

    现在这整件事都需要动画化,我使用了这个答案。我还不知道如何使用这个辅助类来为两个属性设置动画,所以我创建了两个具有相同内容的类(我会在出现信息后立即更新答案):AnimatableLeftHelper和AnimatableTopHelper.
    现在我们使用这些助手来为属性设置动画:

                    <Style>
                        <Setter Property="local:AnimatableLeftHelper.OriginalProperty"
                                Value="{Binding X, Converter={StaticResource XToLeftConverter}}"/>
                        <Setter Property="Canvas.Left"
                                Value="{Binding (local:AnimatableLeftHelper.AnimatedProperty), RelativeSource={RelativeSource Self}}"/>
                        <Setter Property="local:AnimatableTopHelper.OriginalProperty"
                                Value="{Binding Y, Converter={StaticResource YToTopConverter}}"/>
                        <Setter Property="Canvas.Top"
                                Value="{Binding (local:AnimatableTopHelper.AnimatedProperty), RelativeSource={RelativeSource Self}}"/>
                    </Style>
    

    准备好!

    旋转瓷砖

    在这里,我使用了“标准”类MVVM WPF
    Vm:

    abstract class Vm : INotifyPropertyChanged
    {
        protected bool Set<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value))
                return false;
            field = value;
            NotifyPropertyChanged(propertyName);
            return true;
        }
    
        protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
    }
    

    RelayCommand:

    class RelayCommand : ICommand
    {
        protected readonly Predicate<object> _canExecute;
        protected readonly Action<object> _execute;
    
        public event EventHandler CanExecuteChanged;
    
        public RelayCommand(Action<object> execute)
            : this(execute, _ => true) { }
    
        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
            _canExecute = canExecute ?? throw new ArgumentNullException(nameof(canExecute));
        }
    
        public bool CanExecute(object parameter) => _canExecute(parameter);
    
        public void Execute(object parameter) => _execute(parameter);
    
        public void RaiseCanExecuteChanged()
        {
            CanExecuteChanged?.Invoke(this, EventArgs.Empty);
        }
    }
    

    感谢@VladD 的帮助,我们设法使用以下方法重写了示例PointAnimation:

    “瓦”:

    class Tile : Vm
    {
        Point coords;
        public Point Coords
        {
            get => coords;
            set => Set(ref coords, value);
        }
    
        string caption;
        public string Caption
        {
            get => caption;
            set => Set(ref caption, value);
        }
    }
    

    主页VM:

    class MainVM : Vm
    {
        ObservableCollection<Tile> tiles;
        public ObservableCollection<Tile> Tiles
        {
            get => tiles;
            set => Set(ref tiles, value);
        }
    
        public ICommand TurnCommand { get; }
    
        public MainVM()
        {
            Tiles = new ObservableCollection<Tile>
            {
                new Tile { Caption="1", Coords = new Point(0, 0) },
                new Tile { Caption="2", Coords = new Point(1, 0) },
                new Tile { Caption="3", Coords = new Point(2, 0) },
                new Tile { Caption="4", Coords = new Point(2, 1) },
                new Tile { Caption="5", Coords = new Point(2, 2) },
                new Tile { Caption="6", Coords = new Point(1, 2) },
                new Tile { Caption="7", Coords = new Point(0, 2) },
                new Tile { Caption="8", Coords = new Point(0, 1) }
            };
    
            TurnCommand = new DelegateCommand(_ => TurnTiles());
        }
    
        void TurnTiles()
        {
            Dictionary<Point, Point> transitions = new Dictionary<Point, Point>
            {
                [new Point(0, 0)] = new Point(0, 1),
                [new Point(1, 0)] = new Point(0, 0),
                [new Point(2, 0)] = new Point(1, 0),
                [new Point(2, 1)] = new Point(2, 0),
                [new Point(2, 2)] = new Point(2, 1),
                [new Point(1, 2)] = new Point(2, 2),
                [new Point(0, 2)] = new Point(1, 2),
                [new Point(0, 1)] = new Point(0, 2),
            };
            foreach (var tile in Tiles)
                tile.Coords = transitions[tile.Coords];
        }
    }
    

    现在只有一个转换器:

    class XYToPointConverter : IValueConverter
    {
        public double TileWidth { get; set; }
        public double TileHeight { get; set; }
        public double LeftOffset { get; set; }
        public double TopOffset { get; set; }
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var typedValue = (Point)value;
            return new Point(typedValue.X * TileWidth + LeftOffset, typedValue.Y * TileHeight + TopOffset);
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    我添加了更多带有偏移量的属性,以便您可以更灵活地调整图块的位置。

    窗口布局:

    <Window x:Class="WpfTest.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:WpfTest"
            d:DataContext="{d:DesignInstance Type=local:MainVM, IsDesignTimeCreatable=True}"
            mc:Ignorable="d" WindowStartupLocation="CenterScreen"
            Title="MainWindow" Height="400" Width="400">
    
        <Window.Resources>
            <local:XYToPointConverter x:Key="XYToPointConverter"
                                      TileWidth="100" TileHeight="100"
                                      LeftOffset="0" TopOffset="0"/>
        </Window.Resources>
    
        <Grid Margin="5">
            <ItemsControl ItemsSource="{Binding Tiles}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <Canvas/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Border Height="99" Width="99"
                                BorderThickness="1" BorderBrush="Black">
                            <TextBlock Text="{Binding Caption}"
                                       VerticalAlignment="Center" HorizontalAlignment="Center"/>
                        </Border>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                <ItemsControl.ItemContainerStyle>
                    <Style>
                        <Setter Property="local:AnimatablePointHelper.OriginalProperty"
                                Value="{Binding Coords, Converter={StaticResource XYToPointConverter}}"/>
                        <Setter Property="Canvas.Left"
                                Value="{Binding (local:AnimatablePointHelper.AnimatedProperty).X, RelativeSource={RelativeSource Self}}"/>
                        <Setter Property="Canvas.Top"
                                Value="{Binding (local:AnimatablePointHelper.AnimatedProperty).Y, RelativeSource={RelativeSource Self}}"/>
                    </Style>
                </ItemsControl.ItemContainerStyle>
            </ItemsControl>
    
            <Button Content="Rotate" Command="{Binding TurnCommand}"
                    Width="99" Height="99" Margin="100,100,0,0"
                    VerticalAlignment="Top" HorizontalAlignment="Left"/>
        </Grid>
    </Window>
    

    好吧,动画的辅助类:

    static class AnimatablePointHelper
    {
        public static Point GetOriginalProperty(DependencyObject obj) =>
            (Point)obj.GetValue(OriginalPropertyProperty);
        public static void SetOriginalProperty(DependencyObject obj, Point value) =>
            obj.SetValue(OriginalPropertyProperty, value);
        public static readonly DependencyProperty OriginalPropertyProperty =
            DependencyProperty.RegisterAttached(
                "OriginalProperty", typeof(Point), typeof(AnimatablePointHelper),
                new PropertyMetadata(OnOriginalUpdatedStatic));
    
        public static Point GetAnimatedProperty(DependencyObject obj) =>
            (Point)obj.GetValue(AnimatedPropertyProperty);
        public static void SetAnimatedProperty(DependencyObject obj, Point value) =>
            obj.SetValue(AnimatedPropertyProperty, value);
        public static readonly DependencyProperty AnimatedPropertyProperty =
            DependencyProperty.RegisterAttached(
                "AnimatedProperty", typeof(Point), typeof(AnimatablePointHelper));
    
        static void OnOriginalUpdatedStatic(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            Point newValue = (Point)e.NewValue;
            FrameworkElement self = (FrameworkElement)o;
            AnimationTimeline animation =
                new PointAnimation(newValue, new Duration(TimeSpan.FromSeconds(0.3)));
            self.BeginAnimation(AnimatedPropertyProperty, animation);
        }
    }
    

    结果是一样的。

    • 10

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5