2015-04-05 43 views
0

我到处寻找并没有遇到任何东西,但我想知道包含选择矩形的最佳方式,以便它不会超出范围。我有一个应用程序,用户在图像顶部绘制一个选择矩形。矩形也可以移动和调整大小。目前我只使用一个异常处理程序,当超出范围的异常时,它会提醒用户。超出范围的异常只发生在移动绘制的矩形时,我想使其更加简化,即实际的矩形不能在图像之外拖动或调整大小。下面是我的作物控制背后的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); 
     } 
    } 

更新:

我能得到它的工作通过计算选择矩形的大小和位置对其父(帆布)采取与图像相同的尺寸。以下是我添加到我的代码中的内容。

 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被添加,因为如果矩形占据整个图像,仍会抛出异常。

+0

如果您在您的Rectangle上使用MouseDragElementBehavior来处理拖动,那么它有一个方便的ConstrainToParentBounds属性,该属性可以设置您的权利。 – 2015-04-06 14:37:32

+0

@克里斯W.感谢您的提示。我曾尝试添加行为到我的矩形,并将约束设置为父为真,它确实工作,但部分。矩形本身不会在视觉上超出界限,但如果拖动到图像的边缘并撞到墙上,它仍会抛出界限异常。我猜我需要对我的代码进行少量清理才能使其工作。 – mcvanta 2015-04-06 23:18:20

回答

0

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

+0

谢谢你的回复,但是我纠正了我的解释。绘制的矩形不会超出范围,但会在移动或调整绘制的矩形的大小时发生。当我裁剪位图所选部分时,在视图模型中会发生异常。我明白你的观点,但我是C#和XAML的新手,所以我不知道如何做到这一点。你在说我应该将我的选择矩形与图像矩形的位置进行比较。问题是,我的视图模型只接收选择矩形的位置而不是图像,所以我需要在我的用户控件中进行。 – mcvanta 2015-04-06 11:39:35

+0

@mcvanta,你能否给我们提供全功能的例子,它会在正确的地方抛出我们的界限异常(和步骤 - 重现异常)。因为现在的代码对我来说不会产生任何例外,而你对我的问题还不够清楚。 添加的Code方法在任何地方都没有调用,但我想,检查图像大小和纠正rcFrom大小以避免异常 - 也不能解决您的问题。因此,全功能的例子将澄清 - 实际上哪里是麻烦以及你想实现什么。 – dzaraev 2015-04-07 05:33:34