如题,项目中需要实现使用鼠标拖动、缩放一个矩形框,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. Hessian的使用以及理解

    官网 http://hessian.caucho.com/ Hessian的使用以及理解Hessian版本:3.1.5将包括如下的内容: Hessian的基本使用Hessian的原理Hessian和S ...

  2. 如何解决Failed to retrieve MSVC Environment from XXXXXXXX

    升级了新版的Qt5.9.3后,本人的电脑也出了这个问题. 最后通过删除了path中的一些错误.多余的环境变量解决了.(删除了一些mysql的环境变量)

  3. LOJ.6053.简单的函数(Min_25筛)

    题目链接 Min_25筛见这里: https://www.cnblogs.com/cjyyb/p/9185093.html https://www.cnblogs.com/zhoushuyu/p/91 ...

  4. sql去除重复列(行)

    1.存在两条完全相同的纪录   这是最简单的一种情况,用关键字distinct就可以去掉   例子: select distinct * from table(表名) where (条件)   2.存 ...

  5. c++检查内存泄漏

    使用_CrtDumpMemoryLeaks()函数检查内存泄漏 #include <cstdio> #include <cstdlib> #include <crtdbg ...

  6. svn window下过滤文件(如配置文件等)

    第一种: 在资源管理器中,右键一个未加入版本控制文件或目录,并从弹出菜单选择TortoiseSVN →Add to Ignore List,会出现一个子菜单,允许你仅选择该文件或者所有具有相同后缀的文 ...

  7. ES6 模板字面量

    模板字面量 解决的问题 1.多行字符串 一个正式的多行字符串的概念 2.基本的字符串格式化 将变量的值嵌入字符串的能力 3.HTML转义 向HTML插入安全转换后的字符串的能力 (1)基础语法 相当于 ...

  8. webView加载url,加载指定字符串

    //加载url-(void)addWebView{ UIWebView * webView = [[UIWebView alloc] initWithFrame:CGRectMake(, , zScr ...

  9. Cocos Creator_发布到微信小游戏平台

    观看官方教程,地址 传送门: http://docs.cocos.com/creator/manual/zh/publish/publish-wechatgame.html CocosCreator接 ...

  10. python安装虚拟环境virtualenv

    虚拟环境 虚拟环境是一个将不同项目所需求的依赖分别放在独立的地方的一个工具,它给这些工程创建虚拟的Python环境.它解决了“项目X依赖于版本1.x,而项目Y需要项目4.x”的两难问题,而且使你的全局 ...