鼠标相对于使用 MVVM 的 WPF 中的图像的位置

本文关键字:WPF 图像 位置 MVVM 相对于 鼠标 | 更新日期: 2023-09-27 18:31:52

在 wpf 应用程序中的图像上获取鼠标位置时,我试图符合 MVVM 结构。鼠标位置应转换为相对于图像的像素位置。

当Image_MouseMove在 ImagePositionView.xaml.cs 中时,我有这个工作,但我对如何使用 MVVM 结构实现这一点有点不知所措(即使在尝试读取其他线程之后)。

我添加了对 MVVMLight 的引用,希望这将使这项任务更容易,但我以前从未习惯过。

这是我到目前为止所拥有的:

视图:

根据我所看到的添加了这些参考:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:cmd="http://www.galasoft.ch/mvvmlight"

<UserControl x:Class="ImagePixelLocation.View.ImagePositionView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:cmd="http://www.galasoft.ch/mvvmlight"
    xmlns:local="clr-namespace:ImagePixelLocation"
    mc:Ignorable="d" 
    d:DesignHeight="600" d:DesignWidth="1000" Background="White">
   <Grid>
       <Viewbox HorizontalAlignment="Center">
           <Grid Name="ColorImage">
               <Image x:Name="ImageOnDisplay" Source="{Binding ColourImage}" Stretch="UniformToFill" />
           </Grid>
       </Viewbox>
   </Grid>
</UserControl>

视图模型:

ViewModelBase 公开 INofityPropertyChanged 和 IDisposable

using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GalaSoft.MvvmLight;
namespace ImagePixelView.ViewModel
{
    class ImagePositionViewModel : ViewModelBase
    {
        private WriteableBitmap colourBitmap = null;
        public ImageSource ColourImage
        {
            get
            {
                return this.colourBitmap;
            }
        }

        public ManualSelectionViewModel()
        {
            // Open image to writeablebitmap
            string path = @"C:'Some'Path'To'ColorImage.png";
            Stream imageStreamSource = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
            var decoder = new PngBitmapDecoder(imageStreamSource, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
            BitmapSource source = decoder.Frames[0];
            int width = source.PixelWidth;
            int height = source.PixelHeight;
            int stride = source.Format.BitsPerPixel / 8 * width;
            byte[] data = new byte[stride * height];
            source.CopyPixels(data, stride, 0);
            this.colourBitmap = new WriteableBitmap(width, height, 96.0, 96.0, source.Format, null);
            this.colourBitmap.WritePixels(new Int32Rect(0, 0, width, height), data, stride, 0);
        }

        private void Image_MouseMove(object sender, MouseEventArgs e)
        {
            BitmapSource bitmapImage = (BitmapSource)this.ColourImage;
            string xCoord = (e.GetPosition(ImageOnDisplay).X * bitmapImage.PixelWidth / ImageOnDisplay.ActualWidth).ToString();
            string yCoord = (e.GetPosition(ImageOnDisplay).Y * bitmapImage.PixelHeight / ImageOnDisplay.ActualHeight).ToString();
            System.Diagnostics.Debug.WriteLine("mouse location is X:" + xCoord + ", Y:" + yCoord);
        }
    }
}

我想最主要的是如何从ImagePositionViewModel内部访问视图元素ImageOnDisplay

鼠标相对于使用 MVVM 的 WPF 中的图像的位置

我这样做是一种行为。首先,我声明一个视图模型将实现的接口:

public interface IMouseCaptureProxy
{
    event EventHandler Capture;
    event EventHandler Release;
    void OnMouseDown(object sender, MouseCaptureArgs e);
    void OnMouseMove(object sender, MouseCaptureArgs e);
    void OnMouseUp(object sender, MouseCaptureArgs e);
}
public class MouseCaptureArgs
{
    public double X {get; set;}
    public double Y { get; set; }
    public bool LeftButton { get; set; }
    public bool RightButton { get; set; }
}

下面是使用它的行为:

public class MouseCaptureBehavior : Behavior<FrameworkElement>
{
    public static readonly DependencyProperty ProxyProperty = DependencyProperty.RegisterAttached(
        "Proxy",
        typeof(IMouseCaptureProxy),
        typeof(MouseCaptureBehavior),
        new PropertyMetadata(null, OnProxyChanged));
    public static void SetProxy(DependencyObject source, IMouseCaptureProxy value)
    {
        source.SetValue(ProxyProperty, value);
    }
    public static IMouseCaptureProxy GetProxy(DependencyObject source)
    {
        return (IMouseCaptureProxy)source.GetValue(ProxyProperty);
    }
    private static void OnProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.OldValue is IMouseCaptureProxy)
        {
            (e.OldValue as IMouseCaptureProxy).Capture -= OnCapture;
            (e.OldValue as IMouseCaptureProxy).Release -= OnRelease;
        }
        if (e.NewValue is IMouseCaptureProxy)
        {
            (e.NewValue as IMouseCaptureProxy).Capture += OnCapture;
            (e.NewValue as IMouseCaptureProxy).Release += OnRelease;
        }
    }
    static void OnCapture(object sender, EventArgs e)
    {
        var behavior = sender as MouseCaptureBehavior;
        if (behavior != null)
            behavior.AssociatedObject.CaptureMouse();
    }
    static void OnRelease(object sender, EventArgs e)
    {
        var behavior = sender as MouseCaptureBehavior;
        if (behavior != null)
            behavior.AssociatedObject.ReleaseMouseCapture();
    }
    protected override void OnAttached()
    {
        base.OnAttached();
        this.AssociatedObject.PreviewMouseDown += OnMouseDown;
        this.AssociatedObject.PreviewMouseMove += OnMouseMove;
        this.AssociatedObject.PreviewMouseUp += OnMouseUp;
    }
    protected override void OnDetaching()
    {
        base.OnDetaching();
        this.AssociatedObject.PreviewMouseDown -= OnMouseDown;
        this.AssociatedObject.PreviewMouseMove -= OnMouseMove;
        this.AssociatedObject.PreviewMouseUp -= OnMouseUp;
    }
    private void OnMouseDown(object sender, MouseButtonEventArgs e)
    {
        var proxy = GetProxy(this);
        if (proxy != null)
        {
            var pos = e.GetPosition(this.AssociatedObject);
            var args = new MouseCaptureArgs {
                X = pos.X,
                Y = pos.Y,
                LeftButton = (e.LeftButton == MouseButtonState.Pressed),
                RightButton = (e.RightButton == MouseButtonState.Pressed)
            };
            proxy.OnMouseDown(this, args);
        }
    }
    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        var proxy = GetProxy(this);
        if (proxy != null)
        {
            var pos = e.GetPosition(this.AssociatedObject);
            var args = new MouseCaptureArgs {
                X = pos.X,
                Y = pos.Y,
                LeftButton = (e.LeftButton == MouseButtonState.Pressed),
                RightButton = (e.RightButton == MouseButtonState.Pressed)
            };
            proxy.OnMouseMove(this, args);
        }
    }
    private void OnMouseUp(object sender, MouseButtonEventArgs e)
    {
        var proxy = GetProxy(this);
        if (proxy != null)
        {
            var pos = e.GetPosition(this.AssociatedObject);
            var args = new MouseCaptureArgs
            {
                X = pos.X,
                Y = pos.Y,
                LeftButton = (e.LeftButton == MouseButtonState.Pressed),
                RightButton = (e.RightButton == MouseButtonState.Pressed)
            };
            proxy.OnMouseUp(this, args);
        }
    }
}

若要使用此行为,请将其添加到目标 UI 元素,并绑定到实现代理接口的对象。在这种情况下,我让 MainViewModel 实现了接口,所以我只是绑定到它:

<!-- Canvas must have a background, even if it's Transparent -->
<Canvas Background="White" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
    <i:Interaction.Behaviors>
        <behaviors:MouseCaptureBehavior Proxy="{Binding}" />
    </i:Interaction.Behaviors>
视图模型现在需要提供行为将

调用的鼠标处理程序,它还需要提供捕获/释放事件,当视图模型引发时,行为将响应这些事件:

public class MainViewModel : ViewModelBase, IMouseCaptureProxy
{
    public event EventHandler Capture;
    public event EventHandler Release;
    public void OnMouseDown(object sender, MouseCaptureArgs e) {...}
    public void OnMouseMove(object sender, MouseCaptureArgs e) {...}
    public void OnMouseUp(object sender, MouseCaptureArgs e) {...}
}

更新:这应该是不言而喻的,但以防万一:您传递给捕获和释放事件的发件人应该与您通过鼠标按下/移动/向上处理程序收到的发件人相同。传递给捕获/接收的事件参数不会被使用,可以为 null。

加载图像的解决方案,当鼠标使用 MVVM 在图像上移动时,使用 RGB 值显示其 xy 坐标。希望这对某些人来说会有所帮助。

视图模型:

class MainWindowViewModel : ViewModelBase
{
    private Bitmap Img; 
    public ICommand OpenImg { get; set; }
    public MainWindowViewModel()
    {
        OpenImg = new RelayCommand(openImg, (obj) => true);
    }       
    private void openImg(object obj = null)
    {
        OpenFileDialog op = new OpenFileDialog();
        op.Title = "Select a picture";
        op.Filter = "All supported graphics|*.jpg;*.jpeg;*.png;*.bmp;*.tiff|" +
          "JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
          "Portable Network Graphic (*.png)|*.png";
        if (op.ShowDialog() == true)
        {
            ImgPath = op.FileName;
            Img = new Bitmap(ImgPath);
        }
    }
    private string _ImgPath;
    public string ImgPath
    {
        get
        {
            return _ImgPath;
        }
        set
        {
            _ImgPath = value;
            OnPropertyChanged("ImgPath");
        }
    }
    private ICommand _mouseMoveCommand;
    public ICommand MouseMoveCommand
    {
        get
        {
            if (_mouseMoveCommand == null)
            {
              _mouseMoveCommand = new RelayCommand(param => ExecuteMouseMove((MouseEventArgs)param));
            }      
            return _mouseMoveCommand;
        }
        set { _mouseMoveCommand = value; }
    }
    private void ExecuteMouseMove(MouseEventArgs e)
    {
        System.Windows.Point p = e.GetPosition(((IInputElement)e.Source));
        XY = String.Format("X: {0} Y:{1}", (int)p.X, (int)p.Y);
        BitmapData bd = Img.LockBits(new Rectangle(0, 0, Img.Width, Img.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
        unsafe
        {
            byte* ptr = (byte*)bd.Scan0;
            int x = (int)p.X * 3;
            int y = (int)p.Y * bd.Stride;
            RGB = "R: "+ptr[x + y + 2].ToString() + " G: " + ptr[x + y + 1].ToString() + " B: " + ptr[x + y].ToString();
        }
        Img.UnlockBits(bd);
    }
    private string xy;
    public string XY
    {
        get { return xy; }
        set
        {
            xy = value;
            OnPropertyChanged("XY");
        }
    }
    private string rgb;
    public string RGB
    {
        get { return rgb; }
        set
        {
            rgb = value;
            OnPropertyChanged("RGB");
        }
    }
}

MainWindow.xaml

<Window.Resources>
    <vm:MainWindowViewModel x:Key="MainWindowViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource MainWindowViewModel}">
    <Grid.RowDefinitions>
        <RowDefinition Height="30"/>
        <RowDefinition Height="*" />
        <RowDefinition Height="25"/>
    </Grid.RowDefinitions>
    <Grid Grid.Row="0">
        <Menu FontSize="20">
            <MenuItem Header="File">
                <MenuItem Header="Open" Command="{Binding OpenImg}"/>
            </MenuItem>
        </Menu>
    </Grid>
    <Grid Grid.Row="1" Background="LightGray">
        <Viewbox Margin="3,3,3,3">
                    <Image x:Name="img" Stretch="None" Source="{Binding ImgPath}"
                           Model:MouseBehaviour.MouseMoveCommand="{Binding MouseMoveCommand}">     
                    </Image>
        </Viewbox>
    </Grid>
    <Grid Grid.Row="2">
        <StackPanel Orientation="Horizontal">
            <TextBox Focusable="False" Text="{Binding XY}" Width="100"/>
            <TextBox Focusable="False" Text="{Binding RGB}" Width="115"/>
        </StackPanel>
    </Grid>
</Grid>