Reputation: 7
I have a program where the user is able to crop a selected area via mouse or textbox. Right now I'm only able to crop via textbox. Since I'm implementing MVVM I don't know how to implement the crop functionality in my ViewModel. I'm already able to draw rectangles over the image, I implemented this in the code behind. My Problem now is how can I implement the crop functionality in the ViewModel with the values I get from my view code behind?
My Xaml (View)
<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>
The Code behind (View)
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;
}
}
I came across various post threads but none of them could really help me.
Upvotes: 0
Views: 567
Reputation: 1847
You could create a Blend behavior that implements your cropping actions and give the behavior a bindable ICommand property that you invoke on MouseUp (and possibly some other conditions). You could then bind an ICommand property (from your viewmodel) to this property so that your viewmodel will be passed the desired values when the cropping is completed.
https://msdn.microsoft.com/en-us/library/dn195718(v=vs.110).aspx
Here is a "rough" sample that crops an image and saves the cropped image:
ViewModel
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>
Behavior
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
}
This application uses the Expression.Blend.Sdk.WPF and Prism.Core NuGet packages.
Upvotes: 2