LinearGradientBrushAnimationExamples线变画刷动画效果
- 通过对渐变属性Offset、Color、Opacity、StartPoint、EndPoint、S+EPoint组合的动画效果
扩展:
Interactive LinearGradientBrush Example互动线变画刷
关注问题点:
- 画刷的各属性绑定及更新
- 虚线起终点拖动实现及绑定
1、画刷的各属性绑定及更新
- 起始点文本框的输入数字的实时更新及转换,同理文本框坐标数据绑定到终点小圆圈的坐标。
- 设置文本框的键盘弹起事件
- 通过文本框的文本Point.Parse()转换为坐标点,若有错误cath里忽略
- 判断相关属性Point的匹配模式,相对还是绝对坐标,设置到起始点小圆圈的平移坐标里。
<TextBox
Name="StartPointTextBox"
Grid.Column="1" Grid.Row="0"
Text="0.0000,0.0000"
KeyUp="OnStartPointTextBoxKeyUp" />
private void OnStartPointTextBoxKeyUp(object sender, KeyEventArgs args)
{
var t = (TextBox) sender;
try
{
var p = Point.Parse(t.Text);
if (InteractiveLinearGradientBrush.MappingMode == BrushMappingMode.RelativeToBoundingBox)
{
StartPointMarkerTranslateTransform.X = p.X*GradientDisplayElement.ActualWidth;
StartPointMarkerTranslateTransform.Y = p.Y*GradientDisplayElement.ActualHeight;
}
else
{
StartPointMarkerTranslateTransform.X = p.X;
StartPointMarkerTranslateTransform.Y = p.Y;
}
}
catch (InvalidOperationException)
{
// Ignore errors.
}
catch (FormatException)
{
// Ignore errors.
}
}
- ComboBox的绑定源及选择项处理
- 通过绑定源元素的一个属性,使用类型转换功能,特别是利用Enum.GetNames(Type)获取属性的类型(Enum)中的值来获取数据。
- LinearGradientBrush的SpreadMode、ColorInterpolationMode的ComboBox同理如此处理
- 源代码有漏项的,经调试是3个ComboBox的选择项的绑定模式应该是需添加Mode=OneWayToSource,而实际未加。否则运行出现bug。
<ComboBox
Name="MappingModeComboBox"
Grid.Column="1" Grid.Row="2"
SelectedIndex="1"
SelectedItem="{Binding ElementName='InteractiveLinearGradientBrush', Path='MappingMode'}"
ItemsSource="{Binding ElementName='InteractiveLinearGradientBrush', Path='MappingMode', Converter={StaticResource EnumStringConverterResource}}"
Padding="5"
/>
很有创意的获取通用类型Enum中的值的方法:
namespace Brushes
{
[ValueConversion(typeof (object), typeof (string[]))]
public class EnumPossibleValuesToStringArrayConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => new ArrayList(Enum.GetNames(value.GetType()));
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => null;
}
}
- 颜色文本框绑定到线变点的GradientStop的Color属性
<TextBox
Grid.Column="2" Grid.Row="0"
Text="{Binding ElementName='GradientStop1', Path='Color'}" />
滑块绑定到线变点的GradientStop的偏移位置属性
<Slider
Grid.Column="5" Grid.Row="0"
Minimum="0" Maximum="1"
Width="100"
Value="{Binding ElementName='GradientStop1', Path='Offset'}" />
偏移数值绑定到线变点的GradientStop的Offset属性通过Double显示,使用DoubleToString转换器。
<TextBlock
Style="{StaticResource EntryLabelStyle}"
Grid.Column="6" Grid.Row="0"
FontFamily="Courier"
Margin="20,0,0,0"
Text="{Binding ElementName='GradientStop1', Path='Offset',
Converter='{StaticResource DoubleToStringConverterResource}'}" />
2、虚线起终点拖动实现及绑定
- Border的背景色及相关拖动鼠标引起属性变动事件
- 书本左键按下、弹起、 拖动事件处理画刷的起终点变化逻辑
<Border Name="GradientDisplayElement"
SizeChanged="GradientDisplaySizeChanged"
MouseLeftButtonDown="GradientDisplayMouseLeftButtonDown"
MouseLeftButtonUp="GradientDisplayMouseLeftButtonUp"
MouseMove="GradientDisplayMouseMove"
Margin="20"
Height="200" Width="400">
<Border.Background>
<LinearGradientBrush
x:Name="InteractiveLinearGradientBrush"
Changed="OnInteractiveLinearGradientBrushChanged">
<GradientStop x:Name="GradientStop1" Offset="0" Color="Blue" />
<GradientStop x:Name="GradientStop2" Offset="0.5" Color="Purple" />
<GradientStop x:Name="GradientStop3" Offset="1" Color="Red" />
</LinearGradientBrush>
</Border.Background>
Canvas上的Line及小圆圈:
- 设置线条,其起终点坐标绑定到小圆圈的平移转换坐标
- 设置起终点小圆圈的默认位置-10,-10,同时设置需要平移转换的X、Y坐标作为移动坐标,这个小技巧方便文本绑定的位置,不与小圆圈显示重叠。
<Canvas
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Line
Stroke="Black" StrokeThickness="3"
StrokeDashArray="2,1"
X1="{Binding ElementName=StartPointMarkerTranslateTransform, Path=X}"
Y1="{Binding ElementName=StartPointMarkerTranslateTransform, Path=Y}"
X2="{Binding ElementName=EndPointMarkerTranslateTransform, Path=X}"
Y2="{Binding ElementName=EndPointMarkerTranslateTransform, Path=Y}" />
<!-- Marks the brush's StartPoint. -->
<Ellipse
Name="StartPointMarker"
Style="{StaticResource MarkerEllipseStyle}"
Canvas.Left="-10" Canvas.Top="-10">
<Ellipse.RenderTransform>
<TranslateTransform
x:Name="StartPointMarkerTranslateTransform"
X="0" Y="0" />
</Ellipse.RenderTransform>
</Ellipse>
<!-- Marks the brush's EndPointPoint. -->
<Ellipse
Name="EndPointMarker"
Style="{StaticResource MarkerEllipseStyle}"
Canvas.Left="-10" Canvas.Top="-10">
<Ellipse.RenderTransform>
<TranslateTransform
x:Name="EndPointMarkerTranslateTransform"
X="0" Y="0" />
</Ellipse.RenderTransform>
</Ellipse>
<!-- Labels the StartPoint marker. -->
<Label Content="StartPoint">
<Label.RenderTransform>
<TranslateTransform
X="{Binding ElementName=StartPointMarkerTranslateTransform, Path=X}"
Y="{Binding ElementName=StartPointMarkerTranslateTransform, Path=Y}" />
</Label.RenderTransform>
</Label>
<!-- Labels the EndPoint marker. -->
<Label Content="EndPoint">
<Label.RenderTransform>
<TranslateTransform
X="{Binding ElementName=EndPointMarkerTranslateTransform, Path=X}"
Y="{Binding ElementName=EndPointMarkerTranslateTransform, Path=Y}" />
</Label.RenderTransform>
</Label>
</Canvas>
后台代码:
- 在页面加载时进行ComboBox的选择项改变事件。
- 起终点文本框键盘弹起事情(测试此代码无意义,可注释掉):
private void OnPageLoaded(object sender, RoutedEventArgs s)
{
MappingModeComboBox.SelectionChanged += MappingModeChanged;
OnStartPointTextBoxKeyUp(StartPointTextBox, null);
OnEndPointTextBoxKeyUp(EndPointTextBox, null);
}
线性渐变画刷的坐标模式选择值变化处理:
相对坐标与绝对坐标的转换
- 测试发现此选择项改变后起终点文本框的数值无变化,应该源代码遗漏了,添加如下:
// Updates the StartPoint and EndPoint and their markers when
// the user changes the brush's MappingMode.
private void MappingModeChanged(object sender, SelectionChangedEventArgs e)
{
var oldStartPoint = InteractiveLinearGradientBrush.StartPoint;
var newStartPoint = new Point();
var oldEndPoint = InteractiveLinearGradientBrush.EndPoint;
var newEndPoint = new Point();
if (InteractiveLinearGradientBrush.MappingMode ==
BrushMappingMode.RelativeToBoundingBox)
{
// The MappingMode changed from absolute to relative.
// To find the new relative point, divide the old absolute points
// by the painted area's width and height.
newStartPoint.X = oldStartPoint.X/GradientDisplayElement.ActualWidth;
newStartPoint.Y = oldStartPoint.Y/GradientDisplayElement.ActualHeight;
InteractiveLinearGradientBrush.StartPoint = newStartPoint;
newEndPoint.X = oldEndPoint.X/GradientDisplayElement.ActualWidth;
newEndPoint.Y = oldEndPoint.Y/GradientDisplayElement.ActualHeight;
InteractiveLinearGradientBrush.EndPoint = newEndPoint;
}
else
{
// The MappingMode changed from relative to absolute.
// To find the new absolute point, multiply the old relative points
// by the painted area's width and height.
newStartPoint.X = oldStartPoint.X*GradientDisplayElement.ActualWidth;
newStartPoint.Y = oldStartPoint.Y*GradientDisplayElement.ActualHeight;
InteractiveLinearGradientBrush.StartPoint = newStartPoint;
newEndPoint.X = oldEndPoint.X*GradientDisplayElement.ActualWidth;
newEndPoint.Y = oldEndPoint.Y*GradientDisplayElement.ActualHeight;
InteractiveLinearGradientBrush.EndPoint = newEndPoint;
}
// Update the StartPoint and EndPoint display text.
StartPointTextBox.Text = newStartPoint.X.ToString("F4") +
"," + newStartPoint.Y.ToString("F4");
EndPointTextBox.Text = newEndPoint.X.ToString("F4") +
"," + newEndPoint.Y.ToString("F4");
}
键盘弹起事件:
测试发现源码缺少InteractiveLinearGradientBrush.StartPoint = p;会引起MappingMode选择项改变后Text内容还是原来的。
private void OnStartPointTextBoxKeyUp(object sender, KeyEventArgs args)
{
var t = (TextBox) sender;
try
{
var p = Point.Parse(t.Text);
if (InteractiveLinearGradientBrush.MappingMode == BrushMappingMode.RelativeToBoundingBox)
{
StartPointMarkerTranslateTransform.X = p.X*GradientDisplayElement.ActualWidth;
StartPointMarkerTranslateTransform.Y = p.Y*GradientDisplayElement.ActualHeight;
}
else
{
StartPointMarkerTranslateTransform.X = p.X;
StartPointMarkerTranslateTransform.Y = p.Y;
}
InteractiveLinearGradientBrush.StartPoint = p;
}
catch (InvalidOperationException)
{
// Ignore errors.
}
catch (FormatException)
{
// Ignore errors.
}
}
private void OnEndPointTextBoxKeyUp(object sender, KeyEventArgs args)
{
var t = (TextBox) sender;
try
{
var p = Point.Parse(t.Text);
if (InteractiveLinearGradientBrush.MappingMode == BrushMappingMode.RelativeToBoundingBox)
{
EndPointMarkerTranslateTransform.X = p.X*GradientDisplayElement.ActualWidth;
EndPointMarkerTranslateTransform.Y = p.Y*GradientDisplayElement.ActualHeight;
}
else
{
EndPointMarkerTranslateTransform.X = p.X;
EndPointMarkerTranslateTransform.Y = p.Y;
}
InteractiveLinearGradientBrush.EndPoint = p;
}
catch (InvalidOperationException)
{
// Ignore errors.
}
catch (FormatException)
{
// Ignore errors.
}
}
- 获取Border的线变显示尺寸变化事件:
当Border初始化时的尺寸影响到起终点圆圈的位置。
// Update the StartPoint and EndPoint markers when the gradient display
// element's size changes.
private void GradientDisplaySizeChanged(object sender, SizeChangedEventArgs e)
{
// The marker positions only need recalcutated if the brush's MappingMode
// is RelativeToBoundingBox.
if (InteractiveLinearGradientBrush.MappingMode ==
BrushMappingMode.RelativeToBoundingBox)
{
StartPointMarkerTranslateTransform.X =
InteractiveLinearGradientBrush.StartPoint.X*e.NewSize.Width;
StartPointMarkerTranslateTransform.Y =
InteractiveLinearGradientBrush.StartPoint.Y*e.NewSize.Height;
EndPointMarkerTranslateTransform.X =
InteractiveLinearGradientBrush.EndPoint.X*e.NewSize.Width;
EndPointMarkerTranslateTransform.Y =
InteractiveLinearGradientBrush.EndPoint.Y*e.NewSize.Height;
}
}
- 在Border上按下鼠标位置:
- 获取按下的小圆圈对象,此处注册的一个依赖项属性
- 不知是否设置一个Shape字段存储,待后续测试
public static readonly DependencyProperty SelectedMarkerProperty =
DependencyProperty.Register
("SelectedMarker", typeof (Shape), typeof (InteractiveLinearGradientBrushExample),
new PropertyMetadata(null));
// Determine whether the user clicked a marker.
private void GradientDisplayMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.OriginalSource is Shape)
{
SetValue(SelectedMarkerProperty, (Shape) e.OriginalSource);
}
else
SetValue(SelectedMarkerProperty, null);
}
- 鼠标按键弹起事件
- 获取弹起时的相对Border元素的坐标,提取存储的Shape对象进行按下及弹起对象的比较。
- 鼠标同步?
- 判断坐标模式,设置坐标文本框及画刷的坐标
// Determines whether the user just finished dragging a marker. If so,
// this method updates the brush's StartPoint or EndPoint property,
// depending on which marker was dragged.
private void GradientDisplayMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var clickPoint = e.GetPosition(GradientDisplayElement);
var s = (Shape) GetValue(SelectedMarkerProperty);
if (s == EndPointMarker || s == StartPointMarker)
{
var translation = (TranslateTransform) s.RenderTransform;
translation.X = clickPoint.X;
translation.Y = clickPoint.Y;
SetValue(SelectedMarkerProperty, null);
Mouse.Synchronize();
Point p;
if (InteractiveLinearGradientBrush.MappingMode == BrushMappingMode.RelativeToBoundingBox)
{
p = new Point(clickPoint.X/GradientDisplayElement.ActualWidth,
clickPoint.Y/GradientDisplayElement.ActualHeight);
}
else
{
p = clickPoint;
}
if (s == StartPointMarker)
{
InteractiveLinearGradientBrush.StartPoint = p;
StartPointTextBox.Text = p.X.ToString("F4") + "," + p.Y.ToString("F4");
}
else
{
InteractiveLinearGradientBrush.EndPoint = p;
EndPointTextBox.Text = p.X.ToString("F4") + "," + p.Y.ToString("F4");
}
}
}
鼠标移动事件处理:
- 同理如上处理
- 竟然有重复代码,忍不住要重构下了~~
// Update the StartPoint or EndPoint when the user drags one of the
// points with the mouse.
private void GradientDisplayMouseMove(object sender, MouseEventArgs e)
{
var currentPoint = e.GetPosition(GradientDisplayElement);
var s = (Shape) GetValue(SelectedMarkerProperty);
// Determine whether the user dragged a StartPoint or
// EndPoint marker.
if (s == EndPointMarker || s == StartPointMarker)
{
// Move the selected marker to the current mouse position.
var translation = (TranslateTransform) s.RenderTransform;
translation.X = currentPoint.X;
translation.Y = currentPoint.Y;
Mouse.Synchronize();
Point p;
// Calculate the StartPoint or EndPoint.
if (InteractiveLinearGradientBrush.MappingMode ==
BrushMappingMode.RelativeToBoundingBox)
{
// If the MappingMode is relative, compute the relative
// value of the new point.
p = new Point(currentPoint.X/GradientDisplayElement.ActualWidth,
currentPoint.Y/GradientDisplayElement.ActualHeight);
}
else
{
// If the MappingMode is absolute, there's no more
// work to do.
p = currentPoint;
}
if (s == StartPointMarker)
{
// If the selected marker is the StartPoint marker,
// update the brush's StartPoint.
InteractiveLinearGradientBrush.StartPoint = p;
StartPointTextBox.Text = p.X.ToString("F4") + "," + p.Y.ToString("F4");
}
else
{
// Otherwise, update the brush's EndPoint.
InteractiveLinearGradientBrush.EndPoint = p;
EndPointTextBox.Text = p.X.ToString("F4") + "," + p.Y.ToString("F4");
}
}
}
最后是画刷属性的改变事件:
- 更新文本显示的代码内容。
// Update the markup display whenever the brush changes.
private void OnInteractiveLinearGradientBrushChanged(object sender, EventArgs e)
{
if (GradientDisplayElement != null)
{
markupOutputTextBlock.Text =
GenerateLinearGradientBrushMarkup(InteractiveLinearGradientBrush);
}
}
// Helper method that displays the markup of interest for
// creating the specified brush.
private static string GenerateLinearGradientBrushMarkup(LinearGradientBrush theBrush)
{
var sBuilder = new StringBuilder();
sBuilder.Append("<" + theBrush.GetType().Name + "\n" +
" StartPoint=\"" + theBrush.StartPoint + "\"" +
" EndPoint=\"" + theBrush.EndPoint + "\" \n" +
" MappingMode=\"" + theBrush.MappingMode + "\"" +
" SpreadMethod=\"" + theBrush.SpreadMethod + "\"\n" +
" ColorInterpolationMode=\"" + theBrush.ColorInterpolationMode + "\"" +
" Opacity=\"" + theBrush.Opacity.ToString(CultureInfo.InvariantCulture) + "\"" + ">\n");
foreach (var stop in theBrush.GradientStops)
{
sBuilder.Append
(
" <GradientStop Offset=\"" + stop.Offset.ToString("F4")
+ "\" Color=\"" + stop.Color + "\" />\n"
);
}
sBuilder.Append("</LinearGradientBrush>");
return sBuilder.ToString();
}
}
以上,,
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。