使用MVVM裁剪图像

时间:2017-10-26 14:09:21

标签: c# wpf xaml mvvm

我有一个程序,用户可以通过鼠标或文本框裁剪选定的区域。现在我只能通过文本框进行裁剪。自从我实现MVVM以来,我不知道如何在我的ViewModel中实现裁剪功能。我已经能够在图像上绘制矩形,我在后面的代码中实现了这个。 我现在的问题是如何在ViewModel中实现裁剪功能,并从我的视图代码中获取值?

我的Xaml(查看)

<Window x:Class="PROSE.MinimizeImage.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:PROSE"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
    xmlns:wi="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    mc:Ignorable="d"
    Title="MainWindow" Height="575" Width="450">
<StackPanel Margin="10">
    <Grid x:Name="GridLoadedImage" HorizontalAlignment="Left" VerticalAlignment="Top">
        <Grid.RowDefinitions>
            <RowDefinition Height="400" />
            <RowDefinition Height="*"/>
            <RowDefinition MaxHeight="100" />
        </Grid.RowDefinitions>
        <Grid.LayoutTransform>
            <ScaleTransform ScaleX="{Binding ElementName=slider1, Path=Value}" ScaleY="{Binding ElementName=slider1, Path=Value}"/>
        </Grid.LayoutTransform>
        <Canvas Name ="cnvImage">
            <Image x:Name="_image" Margin="10" Source="{Binding CurrentImage.ImagePath, UpdateSourceTrigger=PropertyChanged}"
               HorizontalAlignment="Left" VerticalAlignment="Top" Stretch="Fill" MaxHeight="300"
               MouseDown="_image_MouseDown" MouseMove="_image_MouseMove" MouseUp="_image_MouseUp"/>
        </Canvas>
        <ListBox ItemsSource="{Binding Images}" SelectedItem="{Binding CurrentImage}" Grid.Row="1">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Image Source="{Binding ImagePath}" MaxHeight="40"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
    <WrapPanel>
        <Label Content="X Field" />
        <TextBox Text="{Binding CurrentImage.CropXPosition, UpdateSourceTrigger=PropertyChanged}" Width="40" />
        <Label Content="Y Field" />
        <TextBox Text="{Binding CurrentImage.CropYPosition, UpdateSourceTrigger=PropertyChanged}" Width="40" />
        <Label Content="Height Field" />
        <TextBox Text="{Binding CurrentImage.CropHeight, UpdateSourceTrigger=PropertyChanged}" Width="40" />
        <Label Content="Width Field" />
        <TextBox Text="{Binding CurrentImage.CropWidth, UpdateSourceTrigger=PropertyChanged}" Width="40" />
    </WrapPanel>
    <WrapPanel>
        <Label Content="Width cm" />
        <TextBox Text="{Binding CurrentImage.ResizeWidth, UpdateSourceTrigger=PropertyChanged}" Width="40" />
        <Label Content="Height cm" />
        <TextBox Text="{Binding CurrentImage.ResizeHeight, UpdateSourceTrigger=PropertyChanged}" Width="40" />
    </WrapPanel>
    <WrapPanel>
        <Button Command="{Binding ResizeCommand}">Resize</Button>
        <Button Command="{Binding OpenCommand}" Content="Open"/>
        <Button Command="{Binding CropCommand}">Crop</Button>
        <Button Command="{Binding SaveCommand}" Content="Save"/>
    </WrapPanel>
</StackPanel>

背后的代码(查看)

 public partial class MainWindow : Window
{
    private Point startPoint;
    private Rectangle rectSelectArea;
    private bool isDragging = false;

    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new MainViewModel();
    }

    private void _image_MouseDown(object sender, MouseButtonEventArgs e)
    {
        startPoint = e.GetPosition(cnvImage);

        if (rectSelectArea != null)
            cnvImage.Children.Remove(rectSelectArea);

        rectSelectArea = new Rectangle
        {
            Stroke = Brushes.LightBlue,
            StrokeThickness = 2
        };

        Canvas.SetLeft(rectSelectArea, startPoint.X);
        Canvas.SetTop(rectSelectArea, startPoint.X);
        cnvImage.Children.Add(rectSelectArea);
    }

    private void _image_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Released || rectSelectArea == null)
            return;

        var pos = e.GetPosition(cnvImage);

        //set the position of rectangle
        var x = Math.Min(pos.X, startPoint.X);
        var y = Math.Min(pos.Y, startPoint.Y);

        //set the dimension of rectangle
        var w = Math.Max(pos.X, startPoint.X) - x;
        var h = Math.Max(pos.Y, startPoint.Y) - y;

        rectSelectArea.Width = w;
        rectSelectArea.Height = h;

        Canvas.SetLeft(rectSelectArea, x);
        Canvas.SetTop(rectSelectArea, y);
    }

    private void _image_MouseUp(object sender, MouseButtonEventArgs e)
    {
         // rectSelectArea = null;
    }
}

我遇到了各种帖子,但没有一个能真正帮助我。

1 个答案:

答案 0 :(得分:1)

您可以创建一个实现裁剪操作的Blend行为,并为该行为提供您在MouseUp上调用的可绑定ICommand属性(可能还有一些其他条件)。然后,您可以将ICommand属性(从您的viewmodel)绑定到此属性,以便在完成裁剪时将viewmodel传递给所需的值。

https://msdn.microsoft.com/en-us/library/dn195718(v=vs.110).aspx

这是一个“粗略”样本,用于裁剪图像并保存裁剪后的图像:

<强>视图模型

public class ShellViewModel : BindableBase
{
    public string Title => "Sample";

    public ICommand SelectionCommand => new DelegateCommand<byte[]>(buffer =>
    {
        var path = @"C:\temp\output.bmp";

        File.WriteAllBytes(path, buffer);

        Process.Start(path);
    });
}

<强> XAML

<Window x:Class="Poc.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:viewModels="clr-namespace:Poc.ViewModels"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:behaviors="clr-namespace:Poc.Views.Interactivity.Behaviors"
    mc:Ignorable="d"
    Title="{Binding Title}" Height="350" Width="525">
<Window.DataContext>
    <viewModels:ShellViewModel />
</Window.DataContext>
<Grid>
    <Image x:Name="Image" Source="http://via.placeholder.com/350x150" Stretch="Fill" />
    <Canvas Background="Transparent">
        <i:Interaction.Behaviors>
            <behaviors:CroppingBehavior x:Name="CroppingBehavior" Stroke="White" Thickness="2" SelectionCommand="{Binding SelectionCommand}" TargetElement="{Binding ElementName=Image}" />
        </i:Interaction.Behaviors>
    </Canvas>
    <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="10">
        <Button Command="{Binding ElementName=CroppingBehavior, Path=SaveCommand}"  Padding="10">Save</Button>
        <Button Command="{Binding ElementName=CroppingBehavior, Path=ClearCommand}" Margin="4, 0,0,0" Padding="10">Clear</Button>
    </StackPanel>
</Grid>

<强>行为

public class CroppingBehavior : Behavior<Canvas>
{
    #region Fields

    public DependencyProperty SelectionCommandProperty = DependencyProperty.Register(nameof(SelectionCommand), typeof(ICommand), typeof(CroppingBehavior));

    public DependencyProperty StrokeProperty = DependencyProperty.Register(nameof(Stroke), typeof(Brush), typeof(CroppingBehavior), new PropertyMetadata(Brushes.Fuchsia));
    public DependencyProperty ThicknessProperty = DependencyProperty.Register(nameof(Thickness), typeof(double), typeof(CroppingBehavior), new PropertyMetadata(2d));

    public DependencyProperty StrokeDashCapProperty = DependencyProperty.Register(nameof(StrokeDashCap), typeof(PenLineCap), typeof(CroppingBehavior), new PropertyMetadata(PenLineCap.Round));
    public DependencyProperty StrokeEndLineCapProperty = DependencyProperty.Register(nameof(StrokeEndLineCap), typeof(PenLineCap), typeof(CroppingBehavior), new PropertyMetadata(PenLineCap.Round));
    public DependencyProperty StrokeStartLineCapProperty = DependencyProperty.Register(nameof(StrokeStartLineCap), typeof(PenLineCap), typeof(CroppingBehavior), new PropertyMetadata(PenLineCap.Round));

    public DependencyProperty TargetElementProperty = DependencyProperty.Register(nameof(TargetElement), typeof(FrameworkElement), typeof(CroppingBehavior));

    private Point _startPoint;

    #endregion

    #region Properties

    public ICommand SelectionCommand
    {
        get => (ICommand)GetValue(SelectionCommandProperty);
        set => SetValue(SelectionCommandProperty, value);
    }

    public Brush Stroke
    {
        get => (Brush)GetValue(StrokeProperty);
        set => SetValue(StrokeProperty, value);
    }

    public double Thickness
    {
        get => (double)GetValue(ThicknessProperty);
        set => SetValue(ThicknessProperty, value);
    }

    public PenLineCap StrokeDashCap
    {
        get => (PenLineCap)GetValue(StrokeDashCapProperty);
        set => SetValue(StrokeDashCapProperty, value);
    }

    public PenLineCap StrokeEndLineCap
    {
        get => (PenLineCap)GetValue(StrokeEndLineCapProperty);
        set => SetValue(StrokeEndLineCapProperty, value);
    }

    public PenLineCap StrokeStartLineCap
    {
        get => (PenLineCap)GetValue(StrokeStartLineCapProperty);
        set => SetValue(StrokeStartLineCapProperty, value);
    }

    public ICommand ClearCommand => new DelegateCommand(() => AssociatedObject.Children.Clear());

    public ICommand SaveCommand => new DelegateCommand(OnSave);

    public FrameworkElement TargetElement
    {
        get => (FrameworkElement)GetValue(TargetElementProperty);
        set => SetValue(TargetElementProperty, value);
    }

    #endregion

    #region Methods

    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.MouseDown += OnMouseDown;
        AssociatedObject.MouseMove += OnMouseMove;
        AssociatedObject.MouseUp += OnMouseUp;
    }

    private void OnMouseUp(object sender, MouseButtonEventArgs e)
    {
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            var pos = e.GetPosition(AssociatedObject);

            //set the position of rectangle
            var x = Math.Min(pos.X, _startPoint.X);
            var y = Math.Min(pos.Y, _startPoint.Y);

            //set the dimension of rectangle
            var w = Math.Max(pos.X, _startPoint.X) - x;
            var h = Math.Max(pos.Y, _startPoint.Y) - y;

            var rectangle = new Rectangle
            {
                Stroke = Stroke,
                StrokeThickness = Thickness,
                StrokeDashCap = StrokeDashCap,
                StrokeEndLineCap = StrokeEndLineCap,
                StrokeStartLineCap = StrokeStartLineCap,
                StrokeLineJoin = PenLineJoin.Round,
                Width = w,
                Height = h
            };
            AssociatedObject.Children.Clear();
            AssociatedObject.Children.Add(rectangle);

            Canvas.SetLeft(rectangle, x);
            Canvas.SetTop(rectangle, y);
        }
    }

    private void OnMouseDown(object sender, MouseButtonEventArgs e)
    {
        _startPoint = e.GetPosition(AssociatedObject);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.MouseDown -= OnMouseDown;
        AssociatedObject.MouseUp -= OnMouseMove;
        AssociatedObject.MouseUp -= OnMouseUp;
    }

    private void OnSave()
    {
        if (TargetElement != null)
        {
            var rectangle = AssociatedObject.Children.OfType<Rectangle>().FirstOrDefault();

            if (rectangle != null)
            {
                var bmp = new RenderTargetBitmap((int)TargetElement.ActualWidth, (int)TargetElement.ActualHeight, 96, 96, PixelFormats.Default);

                bmp.Render(TargetElement);

                var cropped = new CroppedBitmap(bmp, new Int32Rect((int)_startPoint.X, (int)_startPoint.Y, (int)rectangle.Width, (int)rectangle.Height));

                using (var stream = new MemoryStream())
                {
                    var encoder = new JpegBitmapEncoder();

                    encoder.Frames.Add(BitmapFrame.Create(cropped));
                    encoder.QualityLevel = 100;
                    encoder.Save(stream);

                    SelectionCommand?.Execute(stream.ToArray());
                }
            }
        }
    }

    #endregion
}

此应用程序使用Expression.Blend.Sdk.WPF和Prism.Core NuGet包。