如题,项目中需要实现使用鼠标拖动、缩放一个矩形框,WPF中没有现成的,那就自己造一个轮子:)

造轮子前先看看Windows自带的画图工具中是怎样做的,如下图:

在被拖动的矩形框四周有9个小框,可以从不同方向拖动来放大缩小矩形框,另外需要注意的是,还有一个框,就是图中虚线的矩形框,这个框,是用来拖动目标控件的;我们要做的,就是模仿画图中的做法,在自定义控件中显示10个框,然后根据鼠标所在的框来处理鼠标输入,实现拖动与放大。

参考这篇博文继续聊WPF——Thumb控件得知,WPF中有现成的拖动控件,可以提供对应的事件(DragDelta & DragCompleted), 就用它了。

还有一个需要考虑的是,我们的这个自定义控件中有10个不同作用的Thumb控件,如何区分事件从哪个Thumb发出来的呢?这样我们才能知道用户希望的操作是拖动,还是缩放,而且缩放也要知道朝哪个方向缩放。可以使用Tag属性,但是它是Object类型的,会涉及到拆箱,所以还是自定义一个CustomThumb。

首先,定义说明拖动方向的枚举:

  1. public enum DragDirection
  2. {
  3. TopLeft = 1,
  4. TopCenter = 2,
  5. TopRight = 4,
  6. MiddleLeft = 16,
  7. MiddleCenter = 32,
  8. MiddleRight = 64,
  9. BottomLeft = 256,
  10. BottomCenter = 512,
  11. BottomRight = 1024,
  12. }
    public enum DragDirection
{
TopLeft = 1,
TopCenter = 2,
TopRight = 4,
MiddleLeft = 16,
MiddleCenter = 32,
MiddleRight = 64,
BottomLeft = 256,
BottomCenter = 512,
BottomRight = 1024,
}

好了,有了这个枚举,就可以知道用户操作的意图了,现在自定义一个CustomThumb。

  1. public class CustomThumb : Thumb
  2. {
  3. public DragDirection DragDirection { get; set; }
  4. }
    public class CustomThumb : Thumb
{
public DragDirection DragDirection { get; set; }
}

这些都弄好了,现在来写自定义控件的模板:

  1. <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  2. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  4. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  5. xmlns:local="clr-namespace:UICommon.Controls"
  6. xmlns:Core="clr-namespace:System;assembly=mscorlib"
  7. mc:Ignorable="d">
  8. <ControlTemplate TargetType="{x:Type local:DragHelperBase}" x:Key="DrapControlHelperTemplate">
  9. <ControlTemplate.Resources>
  10. <Style TargetType="{x:Type Thumb}" x:Key="CornerThumbStyle">
  11. <Setter Property="Width" Value="{Binding CornerWidth, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>
  12. <Setter Property="Height" Value="{Binding CornerWidth, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>
  13. <Setter Property="BorderBrush" Value="{Binding BorderBrush, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>
  14. <Setter Property="BorderThickness" Value="3"/>
  15. <Setter Property="Background" Value="Transparent"/>
  16. <Setter Property="Template">
  17. <Setter.Value>
  18. <ControlTemplate TargetType="{x:Type Thumb}">
  19. <Border SnapsToDevicePixels="True"
  20. Width="{TemplateBinding Width}"
  21. Height="{TemplateBinding Height}"
  22. Background="{TemplateBinding Background}"
  23. BorderBrush="{TemplateBinding BorderBrush}"
  24. BorderThickness="{TemplateBinding BorderThickness}"/>
  25. </ControlTemplate>
  26. </Setter.Value>
  27. </Setter>
  28. </Style>
  29. <Style TargetType="{x:Type Thumb}" x:Key="AreaThumbStyle">
  30. <Setter Property="BorderBrush" Value="{Binding BorderBrush, RelativeSource={RelativeSource TemplatedParent}}"/>
  31. <Setter Property="Background" Value="Transparent"/>
  32. <Setter Property="Padding" Value="0"/>
  33. <Setter Property="Margin" Value="0"/>
  34. <Setter Property="Template">
  35. <Setter.Value>
  36. <ControlTemplate TargetType="{x:Type Thumb}">
  37. <Rectangle Margin="0" Fill="{TemplateBinding Background}" SnapsToDevicePixels="True"
  38. Stroke="{TemplateBinding BorderBrush}" StrokeDashArray="2.0 2.0" Stretch="Fill"
  39. StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderThickness.Top, Mode=OneWay}"/>
  40. </ControlTemplate>
  41. </Setter.Value>
  42. </Setter>
  43. </Style>
  44. </ControlTemplate.Resources>
  45. <Grid x:Name="PART_MainGrid">
  46. <Grid.ColumnDefinitions>
  47. <ColumnDefinition Width="*"/>
  48. <ColumnDefinition Width="*"/>
  49. <ColumnDefinition Width="*"/>
  50. </Grid.ColumnDefinitions>
  51. <Grid.RowDefinitions>
  52. <RowDefinition Height="*"/>
  53. <RowDefinition Height="*"/>
  54. <RowDefinition Height="*"/>
  55. </Grid.RowDefinitions>
  56. <local:CustomThumb DragDirection="MiddleCenter" Grid.RowSpan="3" Grid.ColumnSpan="3" Cursor="SizeAll" Style="{StaticResource AreaThumbStyle}"/>
  57. <local:CustomThumb DragDirection="TopLeft"      Style="{StaticResource CornerThumbStyle}" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left"   VerticalAlignment="Top"    Cursor="SizeNWSE"/>
  58. <local:CustomThumb DragDirection="TopCenter"    Style="{StaticResource CornerThumbStyle}" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Top"    Cursor="SizeNS"/>
  59. <local:CustomThumb DragDirection="TopRight"     Style="{StaticResource CornerThumbStyle}" Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right"  VerticalAlignment="Top"    Cursor="SizeNESW"/>
  60. <local:CustomThumb DragDirection="MiddleLeft"   Style="{StaticResource CornerThumbStyle}" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left"   VerticalAlignment="Center" Cursor="SizeWE"/>
  61. <local:CustomThumb DragDirection="MiddleRight"  Style="{StaticResource CornerThumbStyle}" Grid.Row="1" Grid.Column="2" HorizontalAlignment="Right"  VerticalAlignment="Center" Cursor="SizeWE"/>
  62. <local:CustomThumb DragDirection="BottomLeft"   Style="{StaticResource CornerThumbStyle}" Grid.Row="2" Grid.Column="0" HorizontalAlignment="Left"   VerticalAlignment="Bottom" Cursor="SizeNESW"/>
  63. <local:CustomThumb DragDirection="BottomCenter" Style="{StaticResource CornerThumbStyle}" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Bottom" Cursor="SizeNS"/>
  64. <local:CustomThumb DragDirection="BottomRight"  Style="{StaticResource CornerThumbStyle}" Grid.Row="2" Grid.Column="2" HorizontalAlignment="Right"  VerticalAlignment="Bottom" Cursor="SizeNWSE"/>
  65. </Grid>
  66. </ControlTemplate>
  67. <Style TargetType="{x:Type local:DragHelperBase}"  BasedOn="{StaticResource {x:Type ContentControl}}">
  68. <Setter Property="BorderBrush" Value="Green"/>
  69. <Setter Property="BorderThickness" Value="1"/>
  70. <Setter Property="Padding" Value="0"/>
  71. <Setter Property="Margin" Value="0"/>
  72. <Setter Property="MinHeight" Value="5"/>
  73. <Setter Property="MinWidth" Value="5"/>
  74. <Setter Property="Template" Value="{StaticResource DrapControlHelperTemplate}"/>
  75. </Style>
  76. </ResourceDictionary>
<ResourceDictionary 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:UICommon.Controls"
xmlns:Core="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"> <ControlTemplate TargetType="{x:Type local:DragHelperBase}" x:Key="DrapControlHelperTemplate">
<ControlTemplate.Resources>
<Style TargetType="{x:Type Thumb}" x:Key="CornerThumbStyle">
<Setter Property="Width" Value="{Binding CornerWidth, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>
<Setter Property="Height" Value="{Binding CornerWidth, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>
<Setter Property="BorderBrush" Value="{Binding BorderBrush, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"/>
<Setter Property="BorderThickness" Value="3"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border SnapsToDevicePixels="True"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style> <Style TargetType="{x:Type Thumb}" x:Key="AreaThumbStyle">
<Setter Property="BorderBrush" Value="{Binding BorderBrush, RelativeSource={RelativeSource TemplatedParent}}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Rectangle Margin="0" Fill="{TemplateBinding Background}" SnapsToDevicePixels="True"
Stroke="{TemplateBinding BorderBrush}" StrokeDashArray="2.0 2.0" Stretch="Fill"
StrokeThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderThickness.Top, Mode=OneWay}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ControlTemplate.Resources> <Grid x:Name="PART_MainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions> <local:CustomThumb DragDirection="MiddleCenter" Grid.RowSpan="3" Grid.ColumnSpan="3" Cursor="SizeAll" Style="{StaticResource AreaThumbStyle}"/> <local:CustomThumb DragDirection="TopLeft" Style="{StaticResource CornerThumbStyle}" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Top" Cursor="SizeNWSE"/>
<local:CustomThumb DragDirection="TopCenter" Style="{StaticResource CornerThumbStyle}" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Top" Cursor="SizeNS"/>
<local:CustomThumb DragDirection="TopRight" Style="{StaticResource CornerThumbStyle}" Grid.Row="0" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Top" Cursor="SizeNESW"/> <local:CustomThumb DragDirection="MiddleLeft" Style="{StaticResource CornerThumbStyle}" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Cursor="SizeWE"/>
<local:CustomThumb DragDirection="MiddleRight" Style="{StaticResource CornerThumbStyle}" Grid.Row="1" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Center" Cursor="SizeWE"/> <local:CustomThumb DragDirection="BottomLeft" Style="{StaticResource CornerThumbStyle}" Grid.Row="2" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Bottom" Cursor="SizeNESW"/>
<local:CustomThumb DragDirection="BottomCenter" Style="{StaticResource CornerThumbStyle}" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Bottom" Cursor="SizeNS"/>
<local:CustomThumb DragDirection="BottomRight" Style="{StaticResource CornerThumbStyle}" Grid.Row="2" Grid.Column="2" HorizontalAlignment="Right" VerticalAlignment="Bottom" Cursor="SizeNWSE"/> </Grid>
</ControlTemplate> <Style TargetType="{x:Type local:DragHelperBase}" BasedOn="{StaticResource {x:Type ContentControl}}">
<Setter Property="BorderBrush" Value="Green"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="MinHeight" Value="5"/>
<Setter Property="MinWidth" Value="5"/>
<Setter Property="Template" Value="{StaticResource DrapControlHelperTemplate}"/>
</Style> </ResourceDictionary>

下面编写控件的构造函数,设置DefaultStyleKeyProperty,否则控件加载时将会找不到控件模板

  1. static DragHelperBase()
  2. {
  3. DefaultStyleKeyProperty.OverrideMetadata(typeof(DragHelperBase),
  4. new FrameworkPropertyMetadata(typeof(DragHelperBase)));
  5. }
  6. public DragHelperBase()
  7. {
  8. SetResourceReference(StyleProperty, typeof(DragHelperBase));
  9. }
        static DragHelperBase()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DragHelperBase),
new FrameworkPropertyMetadata(typeof(DragHelperBase)));
}
public DragHelperBase()
{
SetResourceReference(StyleProperty, typeof(DragHelperBase));
}

从控件模板可以看出,10个CustomThumb都在自定义控件的视觉树中,所以我们可以使用Thumb的路由事件,接收鼠标操作的事件并进行处理:

在重写的方法OnApplyTemplate中添加路由事件订阅:

  1. public sealed override void OnApplyTemplate()
  2. {
  3. base.OnApplyTemplate();
  4. MainGrid = GetPartFormTemplate<Grid>("PART_MainGrid");
  5. AddLogicalChild(MainGrid);
  6. AddHandler(Thumb.DragDeltaEvent, new DragDeltaEventHandler(OnDragDelta));
  7. AddHandler(Thumb.DragCompletedEvent, new RoutedEventHandler(OnDragCompleted));
  8. Visibility = Visibility.Collapsed;
  9. }
        public sealed override void OnApplyTemplate()
{
base.OnApplyTemplate(); MainGrid = GetPartFormTemplate<Grid>("PART_MainGrid"); AddLogicalChild(MainGrid); AddHandler(Thumb.DragDeltaEvent, new DragDeltaEventHandler(OnDragDelta));
AddHandler(Thumb.DragCompletedEvent, new RoutedEventHandler(OnDragCompleted)); Visibility = Visibility.Collapsed;
}

可以看到在最后一句的代码中将自定义控件的Visibility属性设为Collapsed,有2个原因,1.用户没选中目标控件前,我们的拖动控件是不应该显示出来的,第二,如果在构造函数中设置这个属性,OnApplyTemplate将不会被调用。

由于这是个抽象类,所以我们需要派生类提供几个必须的方法:

  1. protected abstract bool GetTargetIsEditable();
  2. protected abstract Rect GetTargetActualBound();
  3. protected abstract void SetTargetActualBound(Rect NewBound);
  4. protected abstract void RaisenDragChangingEvent(Rect NewBound);
  5. protected abstract void RaisenDragCompletedEvent(Rect NewBound);
        protected abstract bool GetTargetIsEditable();
protected abstract Rect GetTargetActualBound();
protected abstract void SetTargetActualBound(Rect NewBound);
protected abstract void RaisenDragChangingEvent(Rect NewBound);
protected abstract void RaisenDragCompletedEvent(Rect NewBound);

另外,还要注册2个路由事件,方便其他对象获取目标控件的ActualBound:

  1. #region Drag Event
  2. public static readonly RoutedEvent DragChangingEvent
  3. = EventManager.RegisterRoutedEvent("DragChangingEvent", RoutingStrategy.Bubble, typeof(DragChangedEventHandler), typeof(DragHelperBase));
  4. public event DragChangedEventHandler DragChanging
  5. {
  6. add
  7. {
  8. AddHandler(DragChangingEvent, value);
  9. }
  10. remove
  11. {
  12. RemoveHandler(DragChangingEvent, value);
  13. }
  14. }
  15. public static readonly RoutedEvent DragCompletedEvent
  16. = EventManager.RegisterRoutedEvent("DragCompletedEvent", RoutingStrategy.Bubble, typeof(DragChangedEventHandler), typeof(DragHelperBase));
  17. public event DragChangedEventHandler DragCompleted
  18. {
  19. add
  20. {
  21. AddHandler(DragCompletedEvent, value);
  22. }
  23. remove
  24. {
  25. RemoveHandler(DragCompletedEvent, value);
  26. }
  27. }
  28. #endregion
        #region Drag Event

        public static readonly RoutedEvent DragChangingEvent
= EventManager.RegisterRoutedEvent("DragChangingEvent", RoutingStrategy.Bubble, typeof(DragChangedEventHandler), typeof(DragHelperBase)); public event DragChangedEventHandler DragChanging
{
add
{
AddHandler(DragChangingEvent, value);
}
remove
{
RemoveHandler(DragChangingEvent, value);
}
} public static readonly RoutedEvent DragCompletedEvent
= EventManager.RegisterRoutedEvent("DragCompletedEvent", RoutingStrategy.Bubble, typeof(DragChangedEventHandler), typeof(DragHelperBase)); public event DragChangedEventHandler DragCompleted
{
add
{
AddHandler(DragCompletedEvent, value);
}
remove
{
RemoveHandler(DragCompletedEvent, value);
}
}
#endregion
  1. public class DragChangedEventArgs : RoutedEventArgs
  2. {
  3. public DragChangedEventArgs(RoutedEvent Event, Rect NewBound, object Target = null) : base(Event)
  4. {
  5. this.NewBound = NewBound;
  6. DragTargetElement = Target;
  7. }
  8. public Rect NewBound { get; private set; }
  9. public object DragTargetElement { get; private set; }
  10. }
    public class DragChangedEventArgs : RoutedEventArgs
{
public DragChangedEventArgs(RoutedEvent Event, Rect NewBound, object Target = null) : base(Event)
{
this.NewBound = NewBound;
DragTargetElement = Target;
}
public Rect NewBound { get; private set; } public object DragTargetElement { get; private set; }
}
  1. public delegate void DragChangedEventHandler(object Sender, DragChangedEventArgs e);
    public delegate void DragChangedEventHandler(object Sender, DragChangedEventArgs e);

当用户点击目标控件时,我们的拖动控件应该显示出来,而且,拖动控件的大小、位置应该跟目标控件一致:

  1. #region SetupVisualPropertes
  2. protected void SetupVisualPropertes(double TargetThickness, bool IsEditable)
  3. {
  4. Visibility IsCornerVisibe = IsEditable ? Visibility.Visible : Visibility.Collapsed;
  5. double ActualMargin = (CornerWidth - TargetThickness) / 2.0;
  6. //让9个小框排布在目标边框的中线上
  7. MainGrid.Margin = new Thickness(0 - ActualMargin);
  8. foreach (CustomThumb item in MainGrid.Children)
  9. {
  10. if (item != null)
  11. {
  12. item.BorderThickness = new Thickness(TargetThickness);
  13. if (item.DragDirection == DragDirection.MiddleCenter)
  14. {
  15. item.Margin = new Thickness(ActualMargin);
  16. }
  17. else
  18. {
  19. item.Visibility = IsCornerVisibe;
  20. }
  21. }
  22. }
  23. }
  24. #endregion
        #region SetupVisualPropertes
protected void SetupVisualPropertes(double TargetThickness, bool IsEditable)
{
Visibility IsCornerVisibe = IsEditable ? Visibility.Visible : Visibility.Collapsed; double ActualMargin = (CornerWidth - TargetThickness) / 2.0;
//让9个小框排布在目标边框的中线上
MainGrid.Margin = new Thickness(0 - ActualMargin); foreach (CustomThumb item in MainGrid.Children)
{
if (item != null)
{
item.BorderThickness = new Thickness(TargetThickness); if (item.DragDirection == DragDirection.MiddleCenter)
{
item.Margin = new Thickness(ActualMargin);
}
else
{
item.Visibility = IsCornerVisibe;
}
}
}
}
#endregion

如果目标控件当前不允许编辑,则不要显示四周的9个小框,只显本体区域(虚线框),指示目标控件已经选中但不可以编辑。
当用户拖动鼠标时,处理拖动事件:

  1. private void OnDragDelta(object sender, DragDeltaEventArgs e)
  2. {
  3. if(!GetTargetIsEditable())
  4. {
  5. e.Handled = true;
  6. return;
  7. }
  8. CustomThumb thumb = e.OriginalSource as CustomThumb;
  9. if (thumb == null)
  10. {
  11. return;
  12. }
  13. double VerticalChange = e.VerticalChange;
  14. double HorizontalChange = e.HorizontalChange;
  15. Rect NewBound = Rect.Empty;
  16. if (thumb.DragDirection == DragDirection.MiddleCenter)
  17. {
  18. NewBound = DragElement(HorizontalChange, VerticalChange);
  19. }
  20. else
  21. {
  22. NewBound = ResizeElement(thumb, HorizontalChange, VerticalChange);
  23. }
  24. RaisenDragChangingEvent(NewBound);
  25. SetTargetActualBound(NewBound);
  26. e.Handled = true;
  27. }
  28. private void OnDragCompleted(object sender, RoutedEventArgs e)
  29. {
  30. Rect NewBound = new Rect
  31. {
  32. Y = Canvas.GetTop(this),
  33. X = Canvas.GetLeft(this),
  34. Width = this.ActualWidth,
  35. Height = this.ActualHeight
  36. };
  37. RaisenDragCompletedEvent(NewBound);
  38. e.Handled = true;
  39. }
        private void OnDragDelta(object sender, DragDeltaEventArgs e)
{
if(!GetTargetIsEditable())
{
e.Handled = true;
return;
} CustomThumb thumb = e.OriginalSource as CustomThumb; if (thumb == null)
{
return;
} double VerticalChange = e.VerticalChange;
double HorizontalChange = e.HorizontalChange; Rect NewBound = Rect.Empty; if (thumb.DragDirection == DragDirection.MiddleCenter)
{
NewBound = DragElement(HorizontalChange, VerticalChange);
}
else
{
NewBound = ResizeElement(thumb, HorizontalChange, VerticalChange);
} RaisenDragChangingEvent(NewBound);
SetTargetActualBound(NewBound); e.Handled = true;
} private void OnDragCompleted(object sender, RoutedEventArgs e)
{
Rect NewBound = new Rect
{
Y = Canvas.GetTop(this),
X = Canvas.GetLeft(this),
Width = this.ActualWidth,
Height = this.ActualHeight
}; RaisenDragCompletedEvent(NewBound); e.Handled = true;
}

下面是处理目标控件的拖动:修改目标控件的XY坐标即可

  1. private Rect DragElement(double HorizontalChange, double VerticalChange)
  2. {
  3. Rect TargetActualBound = GetTargetActualBound();
  4. double TopOld  = CorrectDoubleValue(TargetActualBound.Y);
  5. double LeftOld = CorrectDoubleValue(TargetActualBound.X);
  6. double TopNew  = CorrectDoubleValue(TopOld + VerticalChange);
  7. double LeftNew = CorrectDoubleValue(LeftOld + HorizontalChange);
  8. TopNew  = CorrectNewTop(DragHelperParent, TopNew, TargetActualBound.Height);
  9. LeftNew = CorrectNewLeft(DragHelperParent, LeftNew, TargetActualBound.Width);
  10. Canvas.SetTop(this, TopNew);
  11. Canvas.SetLeft(this, LeftNew);
  12. return new Rect
  13. {
  14. Y = TopNew,
  15. X = LeftNew,
  16. Width = TargetActualBound.Width,
  17. Height = TargetActualBound.Height
  18. };
  19. }
        private Rect DragElement(double HorizontalChange, double VerticalChange)
{
Rect TargetActualBound = GetTargetActualBound(); double TopOld = CorrectDoubleValue(TargetActualBound.Y);
double LeftOld = CorrectDoubleValue(TargetActualBound.X);
double TopNew = CorrectDoubleValue(TopOld + VerticalChange);
double LeftNew = CorrectDoubleValue(LeftOld + HorizontalChange); TopNew = CorrectNewTop(DragHelperParent, TopNew, TargetActualBound.Height);
LeftNew = CorrectNewLeft(DragHelperParent, LeftNew, TargetActualBound.Width); Canvas.SetTop(this, TopNew);
Canvas.SetLeft(this, LeftNew); return new Rect
{
Y = TopNew,
X = LeftNew,
Width = TargetActualBound.Width,
Height = TargetActualBound.Height
};
}

下面是处理缩放目标控件,思考一下,其实原理就是从不同的方向放大或缩小目标控件的 Width & Height 属性,或者XY坐标,并且加上限制,不让目标控件超过父控件(在这里是Canvas)的边界:

  1. private Rect ResizeElement(CustomThumb HitedThumb, double HorizontalChange, double VerticalChange)
  2. {
  3. #region Get Old Value
  4. if (HitedThumb == null) return Rect.Empty;
  5. Rect TargetActualBound = GetTargetActualBound();
  6. double TopOld    = CorrectDoubleValue(TargetActualBound.Y);
  7. double LeftOld   = CorrectDoubleValue(TargetActualBound.X);
  8. double WidthOld  = CorrectDoubleValue(TargetActualBound.Width);
  9. double HeightOld = CorrectDoubleValue(TargetActualBound.Height);
  10. double TopNew    = TopOld;
  11. double LeftNew   = LeftOld;
  12. double WidthNew  = WidthOld;
  13. double HeightNew = HeightOld;
  14. #endregion
  15. if (HitedThumb.DragDirection == DragDirection.TopLeft
  16. || HitedThumb.DragDirection == DragDirection.MiddleLeft
  17. || HitedThumb.DragDirection == DragDirection.BottomLeft)
  18. {
  19. ResizeFromLeft(DragHelperParent, LeftOld, WidthOld, HorizontalChange, out LeftNew, out WidthNew);
  20. }
  21. if (HitedThumb.DragDirection == DragDirection.TopLeft
  22. || HitedThumb.DragDirection == DragDirection.TopCenter
  23. || HitedThumb.DragDirection == DragDirection.TopRight)
  24. {
  25. ResizeFromTop(DragHelperParent, TopOld, HeightOld, VerticalChange, out TopNew, out HeightNew);
  26. }
  27. if (HitedThumb.DragDirection == DragDirection.TopRight
  28. || HitedThumb.DragDirection == DragDirection.MiddleRight
  29. || HitedThumb.DragDirection == DragDirection.BottomRight)
  30. {
  31. ResizeFromRight(DragHelperParent, LeftOld, WidthOld, HorizontalChange, out WidthNew);
  32. }
  33. if (HitedThumb.DragDirection == DragDirection.BottomLeft
  34. || HitedThumb.DragDirection == DragDirection.BottomCenter
  35. || HitedThumb.DragDirection == DragDirection.BottomRight)
  36. {
  37. ResizeFromBottom(DragHelperParent, TopOld, HeightOld, VerticalChange, out HeightNew);
  38. }
  39. this.Width = WidthNew;
  40. this.Height = HeightNew;
  41. Canvas.SetTop(this, TopNew);
  42. Canvas.SetLeft(this, LeftNew);
  43. return new Rect
  44. {
  45. X = LeftNew,
  46. Y = TopNew,
  47. Width = WidthNew,
  48. Height = HeightNew
  49. };
  50. }
        private Rect ResizeElement(CustomThumb HitedThumb, double HorizontalChange, double VerticalChange)
{
#region Get Old Value if (HitedThumb == null) return Rect.Empty; Rect TargetActualBound = GetTargetActualBound(); double TopOld = CorrectDoubleValue(TargetActualBound.Y);
double LeftOld = CorrectDoubleValue(TargetActualBound.X);
double WidthOld = CorrectDoubleValue(TargetActualBound.Width);
double HeightOld = CorrectDoubleValue(TargetActualBound.Height); double TopNew = TopOld;
double LeftNew = LeftOld;
double WidthNew = WidthOld;
double HeightNew = HeightOld; #endregion if (HitedThumb.DragDirection == DragDirection.TopLeft
|| HitedThumb.DragDirection == DragDirection.MiddleLeft
|| HitedThumb.DragDirection == DragDirection.BottomLeft)
{
ResizeFromLeft(DragHelperParent, LeftOld, WidthOld, HorizontalChange, out LeftNew, out WidthNew);
} if (HitedThumb.DragDirection == DragDirection.TopLeft
|| HitedThumb.DragDirection == DragDirection.TopCenter
|| HitedThumb.DragDirection == DragDirection.TopRight)
{
ResizeFromTop(DragHelperParent, TopOld, HeightOld, VerticalChange, out TopNew, out HeightNew);
} if (HitedThumb.DragDirection == DragDirection.TopRight
|| HitedThumb.DragDirection == DragDirection.MiddleRight
|| HitedThumb.DragDirection == DragDirection.BottomRight)
{
ResizeFromRight(DragHelperParent, LeftOld, WidthOld, HorizontalChange, out WidthNew);
} if (HitedThumb.DragDirection == DragDirection.BottomLeft
|| HitedThumb.DragDirection == DragDirection.BottomCenter
|| HitedThumb.DragDirection == DragDirection.BottomRight)
{
ResizeFromBottom(DragHelperParent, TopOld, HeightOld, VerticalChange, out HeightNew);
} this.Width = WidthNew;
this.Height = HeightNew;
Canvas.SetTop(this, TopNew);
Canvas.SetLeft(this, LeftNew); return new Rect
{
X = LeftNew,
Y = TopNew,
Width = WidthNew,
Height = HeightNew
};
}

下面是从不同的方向修改目标控件的XY坐标或者Width & Height 属性:

  1. #region Resize Base Methods
  2. #region ResizeFromTop
  3. private static void ResizeFromTop(FrameworkElement Parent, double TopOld, double HeightOld, double VerticalChange, out double TopNew, out double HeightNew)
  4. {
  5. double MiniHeight = 10;
  6. double top = TopOld + VerticalChange;
  7. TopNew = ((top + MiniHeight) > (HeightOld + TopOld)) ? HeightOld + TopOld - MiniHeight : top;
  8. TopNew = TopNew < 0 ? 0 : TopNew;
  9. HeightNew = HeightOld + TopOld - TopNew;
  10. HeightNew = CorrectNewHeight(Parent, TopNew, HeightNew);
  11. }
  12. #endregion
  13. #region ResizeFromLeft
  14. private static void ResizeFromLeft(FrameworkElement Parent, double LeftOld, double WidthOld, double HorizontalChange, out double LeftNew, out double WidthNew)
  15. {
  16. double MiniWidth = 10;
  17. double left = LeftOld + HorizontalChange;
  18. LeftNew = ((left + MiniWidth) > (WidthOld + LeftOld)) ? WidthOld + LeftOld - MiniWidth : left;
  19. LeftNew = LeftNew < 0 ? 0 : LeftNew;
  20. WidthNew = WidthOld + LeftOld - LeftNew;
  21. WidthNew = CorrectNewWidth(Parent, LeftNew, WidthNew);
  22. }
  23. #endregion
  24. #region ResizeFromRight
  25. private static void ResizeFromRight(FrameworkElement Parent, double LeftOld, double WidthOld, double HorizontalChange, out double WidthNew)
  26. {
  27. if (LeftOld + WidthOld + HorizontalChange < Parent.ActualWidth)
  28. {
  29. WidthNew = WidthOld + HorizontalChange;
  30. }
  31. else
  32. {
  33. WidthNew = Parent.ActualWidth - LeftOld;
  34. }
  35. WidthNew = WidthNew < 0 ? 0 : WidthNew;
  36. }
  37. #endregion
  38. #region ResizeFromBottom
  39. private static void ResizeFromBottom(FrameworkElement Parent, double TopOld, double HeightOld, double VerticalChange, out double HeightNew)
  40. {
  41. if (TopOld + HeightOld + VerticalChange < Parent.ActualWidth)
  42. {
  43. HeightNew = HeightOld + VerticalChange;
  44. }
  45. else
  46. {
  47. HeightNew = Parent.ActualWidth - TopOld;
  48. }
  49. HeightNew = HeightNew < 0 ? 0 : HeightNew;
  50. }
  51. #endregion
  52. #region CorrectNewTop
  53. private static double CorrectNewTop(FrameworkElement Parent, double Top, double Height)
  54. {
  55. double NewHeight = ((Top + Height) > Parent.ActualHeight) ? (Parent.ActualHeight - Height) : Top;
  56. return NewHeight < 0 ? 0 : NewHeight;
  57. }
  58. #endregion
  59. #region CorrectNewLeft
  60. private static double CorrectNewLeft(FrameworkElement Parent, double Left, double Width)
  61. {
  62. double NewLeft = ((Left + Width) > Parent.ActualWidth) ? (Parent.ActualWidth - Width) : Left;
  63. return NewLeft < 0 ? 0 : NewLeft;
  64. }
  65. #endregion
  66. #region CorrectNewWidth
  67. private static double CorrectNewWidth(FrameworkElement Parent, double Left, double WidthNewToCheck)
  68. {
  69. double Width = ((Left + WidthNewToCheck) > Parent.ActualWidth) ? (Parent.ActualWidth - Left) : WidthNewToCheck;
  70. return Width < 0 ? 0 : Width;
  71. }
  72. #endregion
  73. #region CorrectNewHeight
  74. private static double CorrectNewHeight(FrameworkElement Parent, double Top, double HeightNewToCheck)
  75. {
  76. double Height = ((Top + HeightNewToCheck) > Parent.ActualHeight) ? (Parent.ActualHeight - Top) : HeightNewToCheck;
  77. return Height < 0 ? 0 : Height;
  78. }
  79. #endregion
  80. #region CorrectDoubleValue
  81. protected static double CorrectDoubleValue(double Value)
  82. {
  83. return (double.IsNaN(Value) || (Value < 0.0)) ? 0 : Value;
  84. }
  85. #endregion
  86. #endregion
        #region Resize Base Methods

        #region ResizeFromTop
private static void ResizeFromTop(FrameworkElement Parent, double TopOld, double HeightOld, double VerticalChange, out double TopNew, out double HeightNew)
{
double MiniHeight = 10; double top = TopOld + VerticalChange;
TopNew = ((top + MiniHeight) > (HeightOld + TopOld)) ? HeightOld + TopOld - MiniHeight : top;
TopNew = TopNew < 0 ? 0 : TopNew; HeightNew = HeightOld + TopOld - TopNew; HeightNew = CorrectNewHeight(Parent, TopNew, HeightNew);
}
#endregion #region ResizeFromLeft
private static void ResizeFromLeft(FrameworkElement Parent, double LeftOld, double WidthOld, double HorizontalChange, out double LeftNew, out double WidthNew)
{
double MiniWidth = 10;
double left = LeftOld + HorizontalChange; LeftNew = ((left + MiniWidth) > (WidthOld + LeftOld)) ? WidthOld + LeftOld - MiniWidth : left; LeftNew = LeftNew < 0 ? 0 : LeftNew; WidthNew = WidthOld + LeftOld - LeftNew; WidthNew = CorrectNewWidth(Parent, LeftNew, WidthNew);
}
#endregion #region ResizeFromRight
private static void ResizeFromRight(FrameworkElement Parent, double LeftOld, double WidthOld, double HorizontalChange, out double WidthNew)
{
if (LeftOld + WidthOld + HorizontalChange < Parent.ActualWidth)
{
WidthNew = WidthOld + HorizontalChange;
}
else
{
WidthNew = Parent.ActualWidth - LeftOld;
} WidthNew = WidthNew < 0 ? 0 : WidthNew;
}
#endregion #region ResizeFromBottom
private static void ResizeFromBottom(FrameworkElement Parent, double TopOld, double HeightOld, double VerticalChange, out double HeightNew)
{
if (TopOld + HeightOld + VerticalChange < Parent.ActualWidth)
{
HeightNew = HeightOld + VerticalChange;
}
else
{
HeightNew = Parent.ActualWidth - TopOld;
} HeightNew = HeightNew < 0 ? 0 : HeightNew;
}
#endregion #region CorrectNewTop
private static double CorrectNewTop(FrameworkElement Parent, double Top, double Height)
{
double NewHeight = ((Top + Height) > Parent.ActualHeight) ? (Parent.ActualHeight - Height) : Top;
return NewHeight < 0 ? 0 : NewHeight;
}
#endregion #region CorrectNewLeft
private static double CorrectNewLeft(FrameworkElement Parent, double Left, double Width)
{
double NewLeft = ((Left + Width) > Parent.ActualWidth) ? (Parent.ActualWidth - Width) : Left; return NewLeft < 0 ? 0 : NewLeft;
}
#endregion #region CorrectNewWidth
private static double CorrectNewWidth(FrameworkElement Parent, double Left, double WidthNewToCheck)
{
double Width = ((Left + WidthNewToCheck) > Parent.ActualWidth) ? (Parent.ActualWidth - Left) : WidthNewToCheck; return Width < 0 ? 0 : Width;
}
#endregion #region CorrectNewHeight
private static double CorrectNewHeight(FrameworkElement Parent, double Top, double HeightNewToCheck)
{
double Height = ((Top + HeightNewToCheck) > Parent.ActualHeight) ? (Parent.ActualHeight - Top) : HeightNewToCheck;
return Height < 0 ? 0 : Height;
}
#endregion #region CorrectDoubleValue
protected static double CorrectDoubleValue(double Value)
{
return (double.IsNaN(Value) || (Value < 0.0)) ? 0 : Value;
}
#endregion #endregion

下面是测试效果:

完整的测试代码可以到我的github下载

http://blog.csdn.net/jtl309/article/details/50651911

在WPF中的Canvas上实现控件的拖动、缩放的更多相关文章

  1. asp.net中遍历界面上所有控件进行属性设置

    * 使用方法: *  前台页面调用方法,重置:    protected void Reset_Click(object sender, EventArgs e)        {           ...

  2. WPF中不规则窗体与WindowsFormsHost控件的兼容问题完美解决方案

    首先先得瑟一下,有关WPF中不规则窗体与WindowsFormsHost控件不兼容的问题,网上给出的解决方案不能满足所有的情况,是有特定条件的,比如  WPF中不规则窗体与WebBrowser控件的兼 ...

  3. WPF 中动态创建和删除控件

    原文:WPF 中动态创建和删除控件 动态创建控件 1.容器控件.RegisterName("Name",要注册的控件)   //注册控件 2.容器控件.FindName(" ...

  4. 在WebBrowser中通过模拟键盘鼠标操控网页中的文件上传控件(转)

    引言 这两天沉迷了Google SketchUp,刚刚玩够,一时兴起,研究了一下WebBrowser. 我在<WebBrowser控件使用技巧分享>一文中曾谈到过“我现在可以通过WebBr ...

  5. WPF中不规则窗体与WebBrowser控件的兼容问题解决办法

    原文:WPF中不规则窗体与WebBrowser控件的兼容问题解决办法 引言 这几天受委托开发一个网络电视项目,要求初步先使用内嵌网页形式实现视频播放和选单,以后再考虑将网页中的所有功能整合进桌面程序. ...

  6. 封装:WPF中可以绑定的BindPassWord控件

    原文:封装:WPF中可以绑定的BindPassWord控件 一.目的:本身自带的PassWord不支持绑定 二.Xaml部分 <UserControl x:Class="HeBianG ...

  7. WPF中增加Month Calendar月历控件

    XAML代码:(这里使用了codeproject.com网站上的一个Dll,你可以在这里下载它:http://www.codeproject.com/cs/miscctrl/MonthCalendar ...

  8. WPF中实现多选ComboBox控件

    在WPF中实现带CheckBox的ComboBox控件,让ComboBox控件可以支持多选. 将ComboBox的ItemsSource属性Binding到一个Book的集合, public clas ...

  9. WPF中Expander的用法和控件模板详解

    一.Expander的用法 在WPF中,Expander是一个很实用的复合控件,可以很方便的实现下拉菜单和导航栏等功能.先介绍简单的用法,而后分析他的控件模板. <Window.Resource ...

随机推荐

  1. linux 服务器安装 nginx

    每次安装 nginx 都在网上找教程,这次特意记录一下安装过程. 第一步:安装依赖 一键安装依赖 yum -y install gcc zlib zlib-devel pcre-devel opens ...

  2. 大数据技术 - MapReduce的Combiner介绍

    本章来简单介绍下 Hadoop MapReduce 中的 Combiner.Combiner 是为了聚合数据而出现的,那为什么要聚合数据呢?因为我们知道 Shuffle 过程是消耗网络IO 和 磁盘I ...

  3. OSFPv3的配置

    实验目的 1.  掌握 OSPFv3 的配置方法 2.  掌握在帧中继环境下 OSPFv3 的配置方法 3.  掌握 OSPFv3 NSSA 的配置方法 4.  掌握外部路由汇总的配置 5.  掌握区 ...

  4. Farewell Party-构造

    Farewell Party 思路 : 转换思路 ,有 a [ i ] 个不相等的 ,那么至少得有 n - a [ i ]个与它相等的. 但是有可能与它拥有相同数目的有很多. 但是为了能够最终 分配成 ...

  5. google像apple 30亿美元购买流量

    google花费30亿美元像apple购买流量作为iphone默认搜索引擎.

  6. LOJ.6074.[2017山东一轮集训Day6]子序列(DP 矩阵乘法)

    题目链接 参考yww的题解.本来不想写来但是他有一些笔误...而且有些地方不太一样就写篇好了. 不知不觉怎么写了这么多... 另外还是有莫队做法的...(虽然可能卡不过) \(60\)分的\(O(n^ ...

  7. BZOJ.4650.[NOI2016]优秀的拆分(后缀数组 思路)

    BZOJ 洛谷 令\(st[i]\)表示以\(i\)为开头有多少个\(AA\)这样的子串,\(ed[i]\)表示以\(i\)结尾有多少个\(AA\)这样的子串.那么\(Ans=\sum_{i=1}^{ ...

  8. BZOJ3565 : [SHOI2014]超能粒子炮

    若$a\leq 1000$,则整个$f$数列会形成$O(a)$段公差为$a$的等差数列. 否则$a^{-1}\leq 1000$,设$ai+b=f(i)$,那么有$i=a^{-1}f(i)-ba^{- ...

  9. C++程序设计方法2:基本语法

    初始化列表 int a[] = {1,2,3}; int a[]{1,2,3} 以上两个式子等价 int a = 3+5: int a = {3+5}; int a(3+5); int a{3+5}; ...

  10. 编程菜鸟的日记-初学尝试编程-寻找等长数组A与B(所含元素相同,顺序不同)相匹配的元素即a[i]=b[j]

    #include <iostream> using namespace std; void matching(int a[],int b[],int N) { int i=0; while ...