WPF mvvm canvas move elements via mouse down, up and move events

FredGrit發表於2024-12-10
//xaml
<Window x:Class="WpfApp58.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:behavior="http://schemas.microsoft.com/xaml/behaviors"
        WindowState="Maximized"
        xmlns:local="clr-namespace:WpfApp58"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <behavior:Interaction.Triggers>
        <behavior:EventTrigger EventName="Closed">
            <behavior:InvokeCommandAction Command="{Binding CloseWindowCommand}"/>
        </behavior:EventTrigger>
    </behavior:Interaction.Triggers>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <Canvas Grid.Row="0" x:Name="cvs" 
                Height="{Binding ActualHeight,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"
                Width="{Binding ActualWidth,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"
                >
            <behavior:Interaction.Triggers>
                <behavior:EventTrigger EventName="MouseDown">
                    <behavior:InvokeCommandAction Command="{Binding MouseDownCommand}"
                                          PassEventArgsToCommand="True"/>
                </behavior:EventTrigger>
                <behavior:EventTrigger EventName="MouseUp">
                    <behavior:InvokeCommandAction Command="{Binding MouseUpCommand}"
                              PassEventArgsToCommand="True"/>
                </behavior:EventTrigger>
                <behavior:EventTrigger EventName="MouseMove">
                    <behavior:InvokeCommandAction Command="{Binding MouseMoveCommand}"
                   PassEventArgsToCommand="True"/>
                </behavior:EventTrigger>
            </behavior:Interaction.Triggers>
        </Canvas>
        <StatusBar Grid.Row="1">
            <StatusBarItem>
                <TextBlock Text="{Binding StatusStr,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" FontSize="20"/>
            </StatusBarItem>            
        </StatusBar>
    </Grid>

</Window>



//cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
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.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace WpfApp58
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            var vm = new CanvasVM(cvs);
            this.DataContext = vm;
        }

        private void cvs_MouseDown(object sender, MouseButtonEventArgs e)
        {

        }

        private void cvs_MouseUp(object sender, MouseButtonEventArgs e)
        {

        }

        private void cvs_MouseMove(object sender, MouseEventArgs e)
        {

        }
    }

    public class CanvasVM : INotifyPropertyChanged
    {
        Canvas cvs;
        Random rnd { get; set; }
        Ellipse elp { get; set; }
        double width, height;
        private bool isMoving = false;
        private bool isCopy = false;
        private int idx = 0;

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string propName)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler?.Invoke(this, new PropertyChangedEventArgs(propName));
            }
        }

        private string statusStr;
        public string StatusStr
        {
            get
            {
                return statusStr;
            }
            set
            {
                if (value != statusStr)
                {
                    statusStr = value;
                    OnPropertyChanged(nameof(StatusStr));
                }
            }
        }

        private Ellipse capturedElp { get; set; }


        public CanvasVM(Canvas cvsValue)
        {
            cvs = cvsValue;
            InitData();
            InitCommands();
        }

        private void InitData()
        {
            width = cvs.ActualWidth;
            height = cvs.ActualHeight;
            rnd = new Random();
            elp = new Ellipse();
            elp.Width = 100;
            elp.Height = 100;
            elp.ToolTip = ++idx;
            elp.Stroke = new SolidColorBrush(Colors.Blue);
            elp.Fill = new SolidColorBrush(Colors.Red);
            elp.StrokeThickness = 5;
            Canvas.SetLeft(elp, rnd.Next(0, (int)width));
            Canvas.SetTop(elp, rnd.Next(0, (int)height));
            if (!cvs.Children.Contains(elp))
            {
                cvs.Children.Add(elp);
            }
        }

        private void InitCommands()
        {
            MouseDownCommand = new DelCommand(MouseDownCommandExecuted);
            MouseUpCommand = new DelCommand(MouseUpCommandExecuted);
            MouseMoveCommand = new DelCommand(MouseMoveCommandExecuted);
            CloseWindowCommand = new DelCommand(CloseWindowCommandExecuted);
        }

        private void CloseWindowCommandExecuted(object obj)
        {
            var msgResult = MessageBox.Show("Are you sure to exit?", "Exit", MessageBoxButton.YesNo,
                MessageBoxImage.Information, MessageBoxResult.Yes);
            if (msgResult == MessageBoxResult.Yes)
            {
                Application.Current?.Shutdown();
            }
        }

        private void MouseMoveCommandExecuted(object obj)
        {
            var e = obj as MouseEventArgs;
            var pt = e.GetPosition(cvs);
            Application.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                StatusStr = $"{pt.X},{pt.Y}";
            }));
            if (e == null)
            {
                return;
            }
            if (Keyboard.Modifiers == ModifierKeys.Control && Keyboard.IsKeyDown(Key.C) && Mouse.LeftButton == MouseButtonState.Pressed)
            {
                isCopy = true;
            }
            else if(Keyboard.Modifiers == ModifierKeys.Control && Mouse.LeftButton==MouseButtonState.Released)
            {
                capturedElp = Mouse.Captured as Ellipse;
                isMoving = true;
            }

            e.Handled = true;
        }

        private void MouseUpCommandExecuted(object obj)
        {
            var e = obj as MouseButtonEventArgs;
            if (e == null)
            {
                return;
            }
            var pt = e.GetPosition(cvs);
            if (Keyboard.Modifiers == ModifierKeys.Control && Mouse.LeftButton == MouseButtonState.Released
                && isCopy)
            {
                Ellipse elp = new Ellipse();
                elp.Width = 100;
                elp.Height = 100;
                elp.ToolTip = ++idx;
                elp.Stroke = new SolidColorBrush(Colors.Blue);
                elp.Fill = new SolidColorBrush(Colors.Red);
                elp.StrokeThickness = 5;
                Canvas.SetLeft(elp, pt.X);
                Canvas.SetTop(elp, pt.Y);
                if (!cvs.Children.Contains(elp))
                {
                    cvs.Children.Add(elp);
                }
                isCopy = false;
            }
            if (isMoving)
            {
                if (capturedElp != null && isMoving)
                {
                    Canvas.SetLeft(capturedElp, pt.X);
                    Canvas.SetTop(capturedElp, pt.Y);
                }
                isMoving = false;
            }
            
            e.Handled = true;
        }

        private void MouseDownCommandExecuted(object obj)
        {
            var e = obj as MouseEventArgs;
            cvs.CaptureMouse();
            Mouse.Capture(elp);
            
            e.Handled = true;
        }

        public DelCommand MouseDownCommand { get; set; }

        public DelCommand MouseUpCommand { get; set; }

        public DelCommand MouseMoveCommand { get; set; }

        public DelCommand CloseWindowCommand { get; set; }
        public List<Visual> FindVisualChildren(DependencyObject dpObj)
        {
            List<Visual> childrenList = new List<Visual>();
            int childrenCount = VisualTreeHelper.GetChildrenCount(dpObj);
            for (int i = 0; i < childrenCount; i++)
            {
                var child = VisualTreeHelper.GetChild(dpObj, i) as Visual;
                if (child != null)
                {
                    childrenList.Add(child);
                    childrenList.AddRange(FindVisualChildren(child));
                }
            }
            return childrenList;
        }
    }

    public class DelCommand : ICommand
    {
        private Action<object> execute;
        private Predicate<object> canExecute;

        public DelCommand(Action<object> executeValue, Predicate<object> canExecuteValue = null)
        {
            this.execute = executeValue;
            this.canExecute = canExecuteValue;
        }
        public event EventHandler CanExecuteChanged
        {
            add
            {
                CommandManager.RequerySuggested += value;
            }
            remove
            {
                CommandManager.RequerySuggested -= value;
            }
        }

        public bool CanExecute(object parameter)
        {
            if (canExecute == null)
            {
                return true;
            }
            return canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            execute(parameter);
        }
    }
}

相關文章