包含图像内的选择矩形

时间:2015-04-05 17:14:47

标签: c# wpf xaml

我到处寻找并且没有遇到任何事情,但我想知道包含选择矩形的最佳方式,以便它不会超出范围。我有一个应用程序是用户在图像上绘制一个选择矩形。矩形也可以移动和调整大小。目前我只使用一个异常处理程序,当捕获超出范围的异常时,它会提醒用户。超出范围的例外仅在移动绘制的矩形时发生,并且我想使其更加流线化,实际的矩形不能在图像外拖动或调整大小。下面是我的裁剪控件的xaml和代码。

作物控制代码背后:

     public partial class CropControl : UserControl
{
    #region Data's
    private bool isDragging = false;
    private Point anchorPoint = new Point();
    private bool MoveRect = false;          //flag which intially set to false which means a crop rectangle is not moved but created.
    private bool MoveInProgress = false;    //flag that is set to true if the crop rect is moving, otherwise false.
    private Point LastPoint;                // The drag's last point
    HitType MouseHitType = HitType.None;    //part of the rectangle under the mouse
    private enum HitType { None, Body, UL, UR, LR, LL, L, R, T, B }; //Enum for the part of the rectangle the mouse is over.
    #endregion

    #region Constructor
    public CropControl()
    {
        InitializeComponent();

    }
    #endregion

    #region Dependency Property
    //Register the Dependency Property
    public static readonly DependencyProperty SelectionProperty =
        DependencyProperty.Register("Selection", typeof(Rect), typeof(CropControl), new PropertyMetadata(default(Rect)));


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

    // 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;
    }
    #endregion
    private Point lastLoc;


    #region MouseLeftButtonDown Event
    private void LoadedImage_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        lastLoc = new Point(Canvas.GetLeft(selectionRectangle), Canvas.GetTop(selectionRectangle));

        //This statement will enable the creation of a new rectangle only if the mouse left
        //button press is outside of a created rectangle and that crop rectangle was initially created.
        //This is known since the HitType if outside the rectangle will always be set to None and the crop rect width > 0.
        //The previous cropping rect will be removed by setting its value to null.
        if (MouseHitType== HitType.None && selectionRectangle.Width>0)
        {
            selectionRectangle.Width = 0;         //set crop rectangle's width to 0
            selectionRectangle.Height = 0;        //set crop rectangle's height to 0
            SetMouseCursor();
            MoveRect = false;           //flag that crop rectangle is not being moved but drawn.
        }

        //This statement test if the crop rectangle is not being dragged and moved. If true it would 
        //set the x and y position of the crop rect in accordance to Canvas. If false it means that
        //crop rectangle was already created and is now being moved to different position in the canvas.
        if (!isDragging && !MoveRect)
        {
            anchorPoint.X = e.GetPosition(BackPanel).X;   //get the x position of the mouse
            anchorPoint.Y = e.GetPosition(BackPanel).Y;   //get the y position of the mouse
            isDragging = true;                      //flag that the user is dragging the mouse to create a rectangle
            BackPanel.Cursor = Cursors.Cross;       //change the cursor to a cross while left button is held down
        }
        else
        {
            MouseHitType = SetHitType(selectionRectangle, e.GetPosition(BackPanel));     //get hittype
            SetMouseCursor();        //set the mouse cursor based on the hittype
            if (MouseHitType == HitType.None) return;       
            LastPoint = e.GetPosition(BackPanel);  
            MoveInProgress = true;      //flag true since rectangle is being moved
        }
    }
    #endregion

    private double CanvasTop, CanvasLeft;

    #region MouseMove Event
    private void LoadedImage_MouseMove(object sender, MouseEventArgs e)
    {
        Point offset = new Point((anchorPoint.X-lastLoc.X),(anchorPoint.Y-lastLoc.Y));
        var newX=(anchorPoint.X+(e.GetPosition(BackPanel).X)-anchorPoint.X);
        var newY=(anchorPoint.Y+(e.GetPosition(BackPanel).Y)-anchorPoint.Y);
        CanvasTop = newX - offset.X;
        CanvasLeft = newY - offset.Y;
        //Statement that checks if crop rect is being created or moved. If moved it will set the 
        //dimension of the rectanlge and if not it would set the location of the new rectangle.
        if (isDragging  && !MoveRect)
        {
            double x = e.GetPosition(BackPanel).X;      //get x position of mouse
            double y = e.GetPosition(BackPanel).Y;      //get y position of mouse

            selectionRectangle.SetValue(Canvas.LeftProperty, Math.Min(x, anchorPoint.X));       //set the bottom
            selectionRectangle.SetValue(Canvas.TopProperty, Math.Min(y, anchorPoint.Y));       //set the top
            selectionRectangle.Width = Math.Abs(x - anchorPoint.X);         //set the width
            selectionRectangle.Height = Math.Abs(y - anchorPoint.Y);        //set the height
            if (selectionRectangle.Visibility != Visibility.Visible)       //make crop rectangle visible if its not.
                selectionRectangle.Visibility = Visibility.Visible;

        }
        else if (!isDragging && MoveRect)
        {

            if (!MoveInProgress)
            {
                MouseHitType = SetHitType(selectionRectangle, e.GetPosition(BackPanel));
                SetMouseCursor();
            }
            else
            {
                // See how much the mouse has moved.
                Point point = e.GetPosition(BackPanel);
                double offset_x = point.X - LastPoint.X;
                double offset_y = point.Y - LastPoint.Y;

                // Get the rectangle's current position.
                double new_x = Canvas.GetLeft(selectionRectangle);     
                double new_y = Canvas.GetTop(selectionRectangle);
                double new_width = selectionRectangle.Width;
                double new_height = selectionRectangle.Height;

                // Update the rectangle.
                switch (MouseHitType)
                {
                    case HitType.Body:
                        new_x += offset_x;
                        new_y += offset_y;
                        break;
                    case HitType.UL:
                        new_x += offset_x;
                        new_y += offset_y;
                        new_width -= offset_x;
                        new_height -= offset_y;
                        break;
                    case HitType.UR:
                        new_y += offset_y;
                        new_width += offset_x;
                        new_height -= offset_y;
                        break;
                    case HitType.LR:
                        new_width += offset_x;
                        new_height += offset_y;
                        break;
                    case HitType.LL:
                        new_x += offset_x;
                        new_width -= offset_x;
                        new_height += offset_y;
                        break;
                    case HitType.L:
                        new_x += offset_x;
                        new_width -= offset_x;
                        break;
                    case HitType.R:
                        new_width += offset_x;
                        break;
                    case HitType.B:
                        new_height += offset_y;
                        break;
                    case HitType.T:
                        new_y += offset_y;
                        new_height -= offset_y;
                        break;
                }

                // Don't use negative width or height.
                if ((new_width > 0) && (new_height > 0))
                {

                    // Update the rectangle.
                    Canvas.SetLeft(selectionRectangle, new_x);
                    Canvas.SetTop(selectionRectangle, new_y);
                    selectionRectangle.Width = new_width;
                    selectionRectangle.Height = new_height;

                    // Save the mouse's new location.
                    LastPoint = point;
                }
            }
        }

    }
    #endregion

    #region MouseLeftButtonUp Event
    private void LoadedImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {

        //statement which checks if the mouse left button action is for either creating or
        //moving the crop rectangle. If true, isDragging=false since the crop rect is created
        //and moverect = true since the created rectangle is ready to be moved.
        if (isDragging && !MoveRect)
        {
            isDragging = false;
            if (selectionRectangle.Width > 0)
            {
                MoveRect = true;
            }
        }
        else
        {
            MoveInProgress = false;  //flags Move in progress as false since rect move action is done.
        }

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

    #region Mutator's
    // Return a HitType value to indicate what is at the point.
    private HitType SetHitType(Rectangle rect, Point point)
    {
        double left = Canvas.GetLeft(selectionRectangle);
        double top = Canvas.GetTop(selectionRectangle);
        double right = left + selectionRectangle.Width;
        double bottom = top + selectionRectangle.Height;

        //statement that checks if cursor is outside the area of the crop rectangle
        //and returns HitType.None.
        if (point.X < left) return HitType.None;
        if (point.X > right) return HitType.None;
        if (point.Y < top) return HitType.None;
        if (point.Y > bottom) return HitType.None;

        const double GAP = 10;  //sets the gap which when mouse over a cursor change is triggered

        //statement that checks where the mouse is located within the rectangle.
        if (point.X - left < GAP)
        {
            // Left edge.
            if (point.Y - top < GAP) return HitType.UL;
            if (bottom - point.Y < GAP) return HitType.LL;
            return HitType.L;
        }
        if (right - point.X < GAP)
        {
            // Right edge.
            if (point.Y - top < GAP) return HitType.UR;
            if (bottom - point.Y < GAP) return HitType.LR;
            return HitType.R;
        }
        if (point.Y - top < GAP) return HitType.T;
        if (bottom - point.Y < GAP) return HitType.B;
        return HitType.Body;
    }

    // Set a mouse cursor appropriate for the current hit type.
    private void SetMouseCursor()
    {
        // See what cursor we should display.
        Cursor desired_cursor = Cursors.Arrow;
        switch (MouseHitType)
        {
            case HitType.None:
                desired_cursor = Cursors.Arrow;
                break;
            case HitType.Body:
                desired_cursor = Cursors.ScrollAll;
                break;
            case HitType.UL:
            case HitType.LR:
                desired_cursor = Cursors.SizeNWSE;
                break;
            case HitType.LL:
            case HitType.UR:
                desired_cursor = Cursors.SizeNESW;
                break;
            case HitType.T:
            case HitType.B:
                desired_cursor = Cursors.SizeNS;
                break;
            case HitType.L:
            case HitType.R:
                desired_cursor = Cursors.SizeWE;
                break;
        }

        // Display the desired cursor.
        if (BackPanel.Cursor != desired_cursor) 
            BackPanel.Cursor = desired_cursor;
    }
    #endregion
}

裁剪控制XAML:

     <UserControl.Resources>
    <Storyboard x:Key="MarchingAnts">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                           Storyboard.TargetName="selectionRectangle" 
                           Storyboard.TargetProperty="(Shape.StrokeDashOffset)" 
                           RepeatBehavior="Forever">
            <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
            <SplineDoubleKeyFrame KeyTime="00:00:00.3000000"
                           Value="10"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</UserControl.Resources>
<UserControl.Triggers>
    <EventTrigger RoutedEvent="FrameworkElement.Loaded">
        <BeginStoryboard Storyboard="{StaticResource MarchingAnts}"/>
    </EventTrigger>
</UserControl.Triggers>
<Canvas Name="BackPanel" Background="Transparent" MouseLeftButtonDown="LoadedImage_MouseLeftButtonDown" MouseMove="LoadedImage_MouseMove" MouseLeftButtonUp="LoadedImage_MouseLeftButtonUp">
    <Rectangle Name="selectionRectangle" Stroke="#FFFFFFFF"
               StrokeThickness="1" StrokeDashOffset="0" 
               Fill="#220000FF" Visibility="Collapsed"
               StrokeDashArray="5"/>
</Canvas>

对于这种困惑感到抱歉,但我改变了我的解释。绘制时矩形不会超出界限,但只有在移动绘制的矩形时才会出现。此异常也在我的视图模型的裁剪方法中捕获,如下所示:

     public void Crop()
    {
        ////Get a copy of the selection in case it changes during execution
        Rect cropSelection = Selection;
        //// use it to crop your image
        Int32Rect rcFrom = new Int32Rect();
        rcFrom.X = (int)((cropSelection.X) * (ImagePath.Width) / (ImagePath.Width));
        rcFrom.Y = (int)((cropSelection.Y) * (ImagePath.Height) / (ImagePath.Height));
        rcFrom.Width = (int)cropSelection.Width;
        rcFrom.Height = (int)cropSelection.Height;

        try
        {
            BitmapSource bs = new CroppedBitmap(ImagePath as BitmapSource, rcFrom);
            CroppedImage = bs;

        }
        catch (Exception e)
        {
            MessageBox.Show("Selection Rectangle is outside the image." + "\n" + "Adjust the cropping rectangle so it's within the boundaries of the Image ", " Error Message", MessageBoxButton.OK, MessageBoxImage.Error);
        }
    }

更新

我能够通过计算选择矩形相对于其父(Canvas)的大小和位置来实现它,它采用与图像相同的大小。以下是我添加到我的代码中的内容。

       double bottom = new_y + selectionRectangle.Height;
                double right = new_x+selectionRectangle.Width;
                if (new_y< 0)
                    new_y = 0;
                if (new_x < 0)
                    new_x = 0;
                if (bottom > BackPanel.ActualHeight)
                    new_y = BackPanel.ActualHeight-selectionRectangle.Height;
                if (right > BackPanel.ActualWidth)
                    new_x = BackPanel.ActualWidth - selectionRectangle.Width;
                if (new_height > BackPanel.ActualHeight)
                    new_height = BackPanel.ActualHeight;
                if (new_width > BackPanel.ActualWidth)
                    new_width = BackPanel.ActualWidth;

添加了new_height和new_width,因为如果矩形占据整个图像,仍然会抛出异常。

1 个答案:

答案 0 :(得分:0)

如果我正确理解了问题 - 当选择rect超出图像rect时,您会向用户显示消息?如果是这样,为什么不检查:如果新选择状态将超出图像区域 - 那么只是不移动选择并保持旧状态?我的意思是 - 将新选择rect的边界位置与图像rect进行比较并做出决定:将您的选择矩移动到新位置(或改变或不改变它的大小)。