How to migrate crop coding from code behind to vie

2019-03-03 09:07发布

问题:

Being new to WPF and MVVM I searched everywhere to find a good answer to my problem. I'm creating a cropping application but I'm trying to migrate the code behind codes to a view model. I was able to bind my mouse button event by using blends interactivity triggers code is below:

    <Grid x:Name="GridLoadedImage" HorizontalAlignment="Left" VerticalAlignment="Top">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseLeftButtonDown">
                        <i:InvokeCommandAction Command="{Binding MouseLeftButtonDownCommand}"/>
                    </i:EventTrigger>
                    <i:EventTrigger EventName="MouseLeftButtonUp">
                        <i:InvokeCommandAction Command="{Binding MouseLeftButtonUpCommand}"/>
                    </i:EventTrigger>
                    <i:EventTrigger EventName="MouseMove">
                        <i:InvokeCommandAction Command="{Binding MouseMoveCommand}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
                <Grid.LayoutTransform>
                    <ScaleTransform ScaleX="{Binding ElementName=slider1, Path=Value}" ScaleY="{Binding ElementName=slider1, Path=Value}"/>
                </Grid.LayoutTransform>
                <Image x:Name="LoadedImage" Margin="10" Source="{Binding ImagePath}"/>
                <Canvas  x:Name="BackPanel" Margin="10">
                    <Rectangle x:Name="selectionRectangle" Stroke="LightBlue" Fill="#220000FF" Visibility="Collapsed"/>
                </Canvas>
            </Grid>

Now my dilema is how to migrate the actual code i used from my code behind which is shown below:

     private void LoadedImage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (isDragging == false)
        {
            anchorPoint.X = e.GetPosition(BackPanel).X;
            anchorPoint.Y = e.GetPosition(BackPanel).Y;
            Canvas.SetZIndex(selectionRectangle, BackPanel.Children.Count);
            isDragging = true;
            BackPanel.Cursor = Cursors.Cross;
        }

    }
    private void LoadedImage_MouseMove(object sender, MouseEventArgs e)
    {
        if (isDragging)
        {
            double x = e.GetPosition(BackPanel).X;
            double y = e.GetPosition(BackPanel).Y;
            selectionRectangle.SetValue(Canvas.LeftProperty, Math.Min(x, anchorPoint.X));
            selectionRectangle.SetValue(Canvas.TopProperty, Math.Min(y, anchorPoint.Y));
            selectionRectangle.Width = Math.Abs(x - anchorPoint.X);
            selectionRectangle.Height = Math.Abs(y - anchorPoint.Y);

            if (selectionRectangle.Visibility != Visibility.Visible)
                selectionRectangle.Visibility = Visibility.Visible;
        }
    }

    private void LoadedImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (isDragging)
        {
            isDragging = false;
            if (selectionRectangle.Width > 0)
            {
                Crop.IsEnabled = true;
                Cut.IsEnabled = true;
                BackPanel.Cursor = Cursors.Arrow;
            }
        }
    }

As you can see I would need to be able to access the x and y coordinates as well as the width and height of the rectangle (which is named selection rectangle). I was thinking of creating the canvas and rectangle inside my viewmodel but that would be against the mvvm structure. I read that I could use attached properties but not familiar with it. What is the best possible way of handling this with respect to MVVM pattern. Currently I'm reading a book by Adan Nathan WPF unleashed 4 which is a great book for beginners like me but i cant seem to find anything that relates to my problem. Thanks for any help

I do have a view model code for my mouse event:

        #region MouseLeftButtonDown
    private bool isDragging = false;
    private Point anchorPoint = new Point();
    private ICommand _mouseLeftButtonDownCommand;
    public ICommand MouseLeftButtonDownCommand
    {
        get
        {
            if (_mouseLeftButtonDownCommand == null)
            {
                _mouseLeftButtonDownCommand = new RelayCommand(param => MouseLeftButtonDown());
            }
            return _mouseLeftButtonDownCommand;
        }

    }
    public void MouseLeftButtonDown()
    {
        if (isDragging == false)
        {
            MessageBox.Show("THis is Mouse Down");
            //anchorPoint.X = e.GetPosition(BackPanel).X;
            //anchorPoint.Y = e.GetPosition(BackPanel).Y;
            isDragging = true;
        }
    }
    #endregion

    #region MouseLeftButtonUp
    private ICommand _mouseLeftButtonUpCommand;
    public ICommand MouseLeftButtonUpCommand
    {
        get
        {
            if (_mouseLeftButtonUpCommand == null)
            {
                _mouseLeftButtonUpCommand = new RelayCommand(param => MouseLeftButtonUp((MouseButtonEventArgs)param));
            }
            return _mouseLeftButtonUpCommand;
        }

    }

    public void MouseLeftButtonUp(MouseButtonEventArgs e)
    {

        if (isDragging)
        {
            MessageBox.Show(e.Source.ToString());
            isDragging = false;
            //if (selectionRectangle.Width > 0)
            //{
            //    Crop.IsEnabled = true;
            //    Cut.IsEnabled = true;
            //    BackPanel.Cursor = Cursors.Arrow;
            //}
        }
    }
    #endregion

    #region MouseMove
    private ICommand _mouseMoveCommand;
    public ICommand MouseMoveCommand
    {
        get
        {
            if (_mouseMoveCommand == null)
            {
                _mouseMoveCommand = new RelayCommand(param => MouseMove());
            }
            return _mouseMoveCommand;
        }

    }
    public void MouseMove()
    {
        if (isDragging)
        {
            //MessageBox.Show("THis is Mouse Move");
            //double x = e.GetPosition(BackPanel).X;
            //double y = e.GetPosition(BackPanel).Y;
            //selectionRectangle.SetValue(Canvas.LeftProperty, Math.Min(x, anchorPoint.X));
            //selectionRectangle.SetValue(Canvas.TopProperty, Math.Min(y, anchorPoint.Y));
            //selectionRectangle.Width = Math.Abs(x - anchorPoint.X);
            //selectionRectangle.Height = Math.Abs(y - anchorPoint.Y);

            //if (selectionRectangle.Visibility != Visibility.Visible)
            //    selectionRectangle.Visibility = Visibility.Visible;
        }
    }
    #endregion

I just commented the actual code and replace it with message boxes just to test if my trigger work which it does. This 3 functions once I figure out how to make it work would draw the cropping rectangle on top of the imaged being cropped. I do have a crop button the will be enabled once the rectangle is completed and this button will be bound to another function that would be the actual cropping function.

回答1:

That's more simple than you may have thought.

What you are doing is an UserControl which userdefined behaviour. So rather than putting that XAML into your Page/View, you implement your own Control which derives from UserControl and implement your code as you have in your code-behind.

Since you are making a custom control, you don't have to follow MVVM for it. In fact, MVVM patter for user controls is discouraged. In your custom control you define may define a Dependency Property which holds an Object of type "SelectionRect" (you shouldn't be using Rect as it's a struct and it doesn't work well with databinding, as it creates a new copy of it each time it changes).

public class CropControl : UserControl 
{
    public Rect Selection
    {
        get { return (Rect)GetValue(SelectionProperty); }
        set { SetValue(SelectionProperty, value); }
    }

    public static readonly DependencyProperty SelectionProperty =
        DependencyProperty.Register("Selection", typeof(Rect), typeof(CropControl), new PropertyMetadata(default(Rect)));

    // this is used, to react on changes from ViewModel. If you assign a  
    // new Rect in your ViewModel you will have to redraw your Rect here
    private static void OnSelectionChanged(System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e)
    {
        Rect newRect = (Rect)e.NewValue;
        Rectangle selectionRectangle = d as Rectangle;

        if(selectionRectangle!=null)
            return;

        selectionRectangle.SetValue(Canvas.LeftProperty, newRect.X);
        selectionRectangle.SetValue(Canvas.TopProperty, newRect.Y);
        selectionRectangle.Width = newRect.Width;
        selectionRectangle.Height = newRect.Height;
    }

    private void LoadedImage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (isDragging == false)
        {
            anchorPoint.X = e.GetPosition(BackPanel).X;
            anchorPoint.Y = e.GetPosition(BackPanel).Y;
            Canvas.SetZIndex(selectionRectangle, BackPanel.Children.Count);
            isDragging = true;
            BackPanel.Cursor = Cursors.Cross;
        }

    }
    private void LoadedImage_MouseMove(object sender, MouseEventArgs e)
    {
        if (isDragging)
        {
            double x = e.GetPosition(BackPanel).X;
            double y = e.GetPosition(BackPanel).Y;
            selectionRectangle.SetValue(Canvas.LeftProperty, Math.Min(x, anchorPoint.X));
            selectionRectangle.SetValue(Canvas.TopProperty, Math.Min(y, anchorPoint.Y));
            selectionRectangle.Width = Math.Abs(x - anchorPoint.X);
            selectionRectangle.Height = Math.Abs(y - anchorPoint.Y);

            if (selectionRectangle.Visibility != Visibility.Visible)
                selectionRectangle.Visibility = Visibility.Visible;

        }
    }

    private void LoadedImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (isDragging)
        {
            isDragging = false;
            if (selectionRectangle.Width > 0)
            {
                Crop.IsEnabled = true;
                Cut.IsEnabled = true;
                BackPanel.Cursor = Cursors.Arrow;
            }

            // Set the Selection to the new rect, when the mouse button has been released
            Selection = new Rect(
                selectionRectangle.GetValue(Canvas.LeftProperty), 
                selectionRectangle.GetValue(Canvas.TopProperty), 
                selectionRectangle.Width,
                selectionRectangle.Height);

        }
    }
}

Notice the only changes were to add Selection = new Rect(...) and the Dependency Property.

Then you can bind it in XAML.

<my:CropControl Selection="{Binding Selection,Mode=TwoWay}"/>

Update: Your ViewModel would look something like

public class MyViewModel : ViewModel 
{
    private Rect selection;
    public Rect Selection 
    {
        get 
        {
            return selection;
        }
        set 
        {
            selection = value;

            // Or whatever the name of your framework/implementation the method is called
            OnPropertyChanged("Selection");

            // Cause ICommands to reevaluate their CanExecute methods
            CommandManager.InvalidateRequerySuggested(); 
        }
    }

    private ICommand cropCommand;
    public ICommand CropCommand {
        get 
        {
            if(cropCommand==null)
                cropCommand = new RelayCommand(Crop, () => Selection.Width > 0); // only allow execution when Selection width > 0

            return cropCommand;
        }
    }

    public void Crop() 
    {
        // Get a copy of the selection in case it changes during execution
        Rect cropSelection = Selection; 

        // use it to crop your image
        ... 
    }
}

Drawing Selection = View Logic (So View) Cropping with a Rect given by CropControl => Presentation/Business Logic (so ViewModel)

Doing so, allows you to reuse your CropControl in other applications. If you put your "selectionRect" drawing code into your ViewModel (which may be possible, but causes hard to read and maintain code), then you can't reuse it in other application, since your ViewModels are specific to your application.

Hope that helps.



回答2:

MVVM means separating View from ViewModel. The example you give is typically a View only code.

Your example seems to be a kind of selection tool, I deduce you want to get the selected content back, or at least the cropping coordinates. So the best is to transform your code in a custom control exposing a Rect DependencyProperty for the crop coordinates, and in your view model, you should expose a Rect property holding the cropping rectangle coordinates, and then Bind it to your cropping control DepencyProperty.

The view is about interacting with visual aspects. The ViewModel is about holding and working with the data used by the view.



标签: c# wpf xaml mvvm