鼠标相对于使用 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
。
我这样做是一种行为。首先,我声明一个视图模型将实现的接口:
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>