WPF进阶教程 - 使用Decorator自定义带三角形的边框
原文:WPF进阶教程 - 使用Decorator自定义带三角形的边框
写下来,备忘。
Decorator,有装饰器、装饰品的意思,很容易让人联想到设计模式里面的装饰器模式。Decorator类负责包装某个UI元素,用来提供额外的行为。它有一个类型为UIElement的Child属性,其中含有待包装的内容。Decorator可以被用于添加简单的视觉装饰,比如Border边框,或者更为复杂的行为,比如ViewBox、AdornerDecorator。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media; namespace WpfDemo
{
public sealed class AngleBorder : Decorator
{
public enum EnumPlacement
{
/// <summary>
/// 左上
/// </summary>
LeftTop,
/// <summary>
/// 左中
/// </summary>
LeftBottom,
/// <summary>
/// 左下
/// </summary>
LeftCenter,
/// <summary>
/// 右上
/// </summary>
RightTop,
/// <summary>
/// 右下
/// </summary>
RightBottom,
/// <summary>
/// 右中
/// </summary>
RightCenter,
/// <summary>
/// 上左
/// </summary>
TopLeft,
/// <summary>
/// 上中
/// </summary>
TopCenter,
/// <summary>
/// 上右
/// </summary>
TopRight,
/// <summary>
/// 下左
/// </summary>
BottomLeft,
/// <summary>
/// 下中
/// </summary>
BottomCenter,
/// <summary>
/// 下右
/// </summary>
BottomRight,
} #region 依赖属性
public static readonly DependencyProperty PlacementProperty =
DependencyProperty.Register("Placement", typeof(EnumPlacement), typeof(AngleBorder),
new FrameworkPropertyMetadata(EnumPlacement.RightCenter, FrameworkPropertyMetadataOptions.AffectsRender, OnDirectionPropertyChangedCallback)); public EnumPlacement Placement
{
get { return (EnumPlacement)GetValue(PlacementProperty); }
set { SetValue(PlacementProperty, value); }
} public static readonly DependencyProperty TailWidthProperty =
DependencyProperty.Register("TailWidth", typeof(double), typeof(AngleBorder), new PropertyMetadata(10d));
/// <summary>
/// 尾巴的宽度,默认值为7
/// </summary>
public double TailWidth
{
get { return (double)GetValue(TailWidthProperty); }
set { SetValue(TailWidthProperty, value); }
}
public static readonly DependencyProperty TailHeightProperty =
DependencyProperty.Register("TailHeight", typeof(double), typeof(AngleBorder), new PropertyMetadata(10d));
/// <summary>
/// 尾巴的高度,默认值为10
/// </summary>
public double TailHeight
{
get { return (double)GetValue(TailHeightProperty); }
set { SetValue(TailHeightProperty, value); }
} public static readonly DependencyProperty TailVerticalOffsetProperty =
DependencyProperty.Register("TailVerticalOffset", typeof(double), typeof(AngleBorder), new PropertyMetadata(13d));
/// <summary>
/// 尾巴距离顶部的距离,默认值为10
/// </summary>
public double TailVerticalOffset
{
get { return (double)GetValue(TailVerticalOffsetProperty); }
set { SetValue(TailVerticalOffsetProperty, value); }
}
public static readonly DependencyProperty TailHorizontalOffsetProperty =
DependencyProperty.Register("TailHorizontalOffset", typeof(double), typeof(AngleBorder),
new PropertyMetadata(12d));
/// <summary>
/// 尾巴距离顶部的距离,默认值为10
/// </summary>
public double TailHorizontalOffset
{
get { return (double)GetValue(TailHorizontalOffsetProperty); }
set { SetValue(TailHorizontalOffsetProperty, value); }
}
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.Register("Background", typeof(Brush), typeof(AngleBorder)
, new PropertyMetadata(new SolidColorBrush(Color.FromRgb(255, 255, 255))));
/// <summary>
/// 背景色,默认值为#FFFFFF,白色
/// </summary>
public Brush Background
{
get { return (Brush)GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
} public static readonly DependencyProperty PaddingProperty =
DependencyProperty.Register("Padding", typeof(Thickness), typeof(AngleBorder)
, new PropertyMetadata(new Thickness(10, 5, 10, 5)));
/// <summary>
/// 内边距
/// </summary>
public Thickness Padding
{
get { return (Thickness)GetValue(PaddingProperty); }
set { SetValue(PaddingProperty, value); }
} public static readonly DependencyProperty BorderBrushProperty =
DependencyProperty.Register("BorderBrush", typeof(Brush), typeof(AngleBorder)
, new PropertyMetadata(default(Brush)));
/// <summary>
/// 边框颜色
/// </summary>
public Brush BorderBrush
{
get { return (Brush)GetValue(BorderBrushProperty); }
set { SetValue(BorderBrushProperty, value); }
} public static readonly DependencyProperty BorderThicknessProperty =
DependencyProperty.Register("BorderThickness", typeof(Thickness), typeof(AngleBorder), new PropertyMetadata(new Thickness(1d)));
/// <summary>
/// 边框大小
/// </summary>
public Thickness BorderThickness
{
get { return (Thickness)GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
} public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(System.Windows.CornerRadius)
, typeof(AngleBorder), new PropertyMetadata(new CornerRadius(0)));
/// <summary>
/// 边框大小
/// </summary>
public System.Windows.CornerRadius CornerRadius
{
get { return (System.Windows.CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
#endregion #region 方法重写
/// <summary>
/// 该方法用于测量整个控件的大小
/// </summary>
/// <param name="constraint"></param>
/// <returns>控件的大小</returns>
protected override Size MeasureOverride(Size constraint)
{
Thickness padding = this.Padding; Size result = new Size();
if (Child != null)
{
//测量子控件的大小
Child.Measure(constraint); //三角形在左边与右边的,整个容器的宽度则为:里面子控件的宽度 + 设置的padding + 三角形的宽度
//三角形在上面与下面的,整个容器的高度则为:里面子控件的高度 + 设置的padding + 三角形的高度
switch (Placement)
{
case EnumPlacement.LeftTop:
case EnumPlacement.LeftBottom:
case EnumPlacement.LeftCenter:
case EnumPlacement.RightTop:
case EnumPlacement.RightBottom:
case EnumPlacement.RightCenter:
result.Width = Child.DesiredSize.Width + padding.Left + padding.Right + this.TailWidth;
result.Height = Child.DesiredSize.Height + padding.Top + padding.Bottom;
break;
case EnumPlacement.TopLeft:
case EnumPlacement.TopCenter:
case EnumPlacement.TopRight:
case EnumPlacement.BottomLeft:
case EnumPlacement.BottomCenter:
case EnumPlacement.BottomRight:
result.Width = Child.DesiredSize.Width + padding.Left + padding.Right;
result.Height = Child.DesiredSize.Height + padding.Top + padding.Bottom + this.TailHeight;
break;
default:
break;
}
}
return result;
} /// <summary>
/// 设置子控件的大小与位置
/// </summary>
/// <param name="arrangeSize"></param>
/// <returns></returns>
protected override Size ArrangeOverride(Size arrangeSize)
{
Thickness padding = this.Padding;
if (Child != null)
{
switch (Placement)
{
case EnumPlacement.LeftTop:
case EnumPlacement.LeftBottom:
case EnumPlacement.LeftCenter:
Child.Arrange(new Rect(new Point(padding.Left + this.TailWidth, padding.Top), Child.DesiredSize));
//ArrangeChildLeft();
break;
case EnumPlacement.RightTop:
case EnumPlacement.RightBottom:
case EnumPlacement.RightCenter:
ArrangeChildRight(padding);
break;
case EnumPlacement.TopLeft:
case EnumPlacement.TopRight:
case EnumPlacement.TopCenter:
Child.Arrange(new Rect(new Point(padding.Left, this.TailHeight + padding.Top), Child.DesiredSize));
break;
case EnumPlacement.BottomLeft:
case EnumPlacement.BottomRight:
case EnumPlacement.BottomCenter:
Child.Arrange(new Rect(new Point(padding.Left, padding.Top), Child.DesiredSize));
break;
default:
break;
}
}
return arrangeSize;
} private void ArrangeChildRight(Thickness padding)
{
double x = padding.Left;
double y = padding.Top; if (!Double.IsNaN(this.Height) && this.Height != 0)
{
y = (this.Height - (Child.DesiredSize.Height)) / 2;
} Child.Arrange(new Rect(new Point(x, y), Child.DesiredSize));
} /// <summary>
/// 绘制控件
/// </summary>
/// <param name="drawingContext"></param>
protected override void OnRender(DrawingContext drawingContext)
{
if (Child != null)
{
Geometry cg = null;
Brush brush = null;
//DpiScale dpi = base.getd();
Pen pen = new Pen(); pen.Brush = this.BorderBrush;
//pen.Thickness = BorderThickness * 0.5;
pen.Thickness = AngleBorder.RoundLayoutValue(BorderThickness.Left, DoubleUtil.DpiScaleX); switch (Placement)
{
case EnumPlacement.LeftTop:
case EnumPlacement.LeftBottom:
case EnumPlacement.LeftCenter:
//生成小尾巴在左侧的图形和底色
cg = CreateGeometryTailAtLeft();
brush = CreateFillBrush();
break;
case EnumPlacement.RightTop:
case EnumPlacement.RightCenter:
case EnumPlacement.RightBottom:
//生成小尾巴在右侧的图形和底色
cg = CreateGeometryTailAtRight();
brush = CreateFillBrush();
break;
case EnumPlacement.TopLeft:
case EnumPlacement.TopCenter:
case EnumPlacement.TopRight:
//生成小尾巴在右侧的图形和底色
cg = CreateGeometryTailAtTop();
brush = CreateFillBrush();
break;
case EnumPlacement.BottomLeft:
case EnumPlacement.BottomCenter:
case EnumPlacement.BottomRight:
//生成小尾巴在右侧的图形和底色
cg = CreateGeometryTailAtBottom();
brush = CreateFillBrush();
break;
default:
break;
}
GuidelineSet guideLines = new GuidelineSet();
drawingContext.PushGuidelineSet(guideLines);
drawingContext.DrawGeometry(brush, pen, cg);
}
}
#endregion private static double RoundLayoutValue(double value, double dpiScale)
{
double num;
if (!AngleBorder.AreClose(dpiScale, 1.0))
{
num = Math.Round(value * dpiScale) / dpiScale;
if (double.IsInfinity(num) || AngleBorder.AreClose(num, 1.7976931348623157E+308))
{
num = value;
}
}
else
{
num = Math.Round(value);
}
return num;
} static bool AreClose(double value1, double value2)
{
if (value1 == value2)
{
return true;
}
double num = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * 2.2204460492503131E-16;
double num2 = value1 - value2;
return -num < num2 && num > num2;
} #region 私有方法
private Geometry CreateGeometryTailAtRight()
{
CombinedGeometry result = new CombinedGeometry(); //三角形默认居中
this.TailVerticalOffset = (this.ActualHeight - this.TailHeight) / 2; #region 绘制三角形
Point arcPoint1 = new Point(this.ActualWidth - TailWidth, TailVerticalOffset);
Point arcPoint2 = new Point(this.ActualWidth, TailVerticalOffset + TailHeight / 2);
Point arcPoint3 = new Point(this.ActualWidth - TailWidth, TailVerticalOffset + TailHeight); LineSegment as1_2 = new LineSegment(arcPoint2, false);
LineSegment as2_3 = new LineSegment(arcPoint3, false); PathFigure pf1 = new PathFigure();
pf1.IsClosed = false;
pf1.StartPoint = arcPoint1;
pf1.Segments.Add(as1_2);
pf1.Segments.Add(as2_3); PathGeometry pg1 = new PathGeometry();
pg1.Figures.Add(pf1);
#endregion #region 绘制矩形边框
RectangleGeometry rg2 = new RectangleGeometry(new Rect(0, 0, this.ActualWidth - TailWidth, this.ActualHeight)
, CornerRadius.TopLeft, CornerRadius.BottomRight, new TranslateTransform(0.5, 0.5));
#endregion #region 合并两个图形
result.Geometry1 = pg1;
result.Geometry2 = rg2;
result.GeometryCombineMode = GeometryCombineMode.Union;
#endregion return result;
} private Geometry CreateGeometryTailAtLeft()
{
CombinedGeometry result = new CombinedGeometry(); switch (this.Placement)
{
case EnumPlacement.LeftTop:
//不做任何处理
break;
case EnumPlacement.LeftBottom:
this.TailVerticalOffset = this.ActualHeight - this.TailHeight - this.TailVerticalOffset;
break;
case EnumPlacement.LeftCenter:
this.TailVerticalOffset = (this.ActualHeight - this.TailHeight) / 2;
break;
} #region 绘制三角形
Point arcPoint1 = new Point(TailWidth, TailVerticalOffset);
Point arcPoint2 = new Point(0, TailVerticalOffset + TailHeight / 2);
Point arcPoint3 = new Point(TailWidth, TailVerticalOffset + TailHeight); LineSegment as1_2 = new LineSegment(arcPoint2, false);
LineSegment as2_3 = new LineSegment(arcPoint3, false); PathFigure pf = new PathFigure();
pf.IsClosed = false;
pf.StartPoint = arcPoint1;
pf.Segments.Add(as1_2);
pf.Segments.Add(as2_3); PathGeometry g1 = new PathGeometry();
g1.Figures.Add(pf);
#endregion #region 绘制矩形边框
RectangleGeometry g2 = new RectangleGeometry(new Rect(TailWidth, 0, this.ActualWidth - this.TailWidth, this.ActualHeight)
, CornerRadius.TopLeft, CornerRadius.BottomRight);
#endregion #region 合并两个图形
result.Geometry1 = g1;
result.Geometry2 = g2;
result.GeometryCombineMode = GeometryCombineMode.Union;
#endregion return result;
} private Geometry CreateGeometryTailAtTop()
{
CombinedGeometry result = new CombinedGeometry(); switch (this.Placement)
{
case EnumPlacement.TopLeft:
break;
case EnumPlacement.TopCenter:
this.TailHorizontalOffset = (this.ActualWidth - this.TailWidth) / 2;
break;
case EnumPlacement.TopRight:
this.TailHorizontalOffset = this.ActualWidth - this.TailWidth - this.TailHorizontalOffset;
break;
} #region 绘制三角形
Point anglePoint1 = new Point(this.TailHorizontalOffset, this.TailHeight);
Point anglePoint2 = new Point(this.TailHorizontalOffset + (this.TailWidth / 2), 0);
Point anglePoint3 = new Point(this.TailHorizontalOffset + this.TailWidth, this.TailHeight); LineSegment as1_2 = new LineSegment(anglePoint2, true);
LineSegment as2_3 = new LineSegment(anglePoint3, true); PathFigure pf = new PathFigure();
pf.IsClosed = false;
pf.StartPoint = anglePoint1;
pf.Segments.Add(as1_2);
pf.Segments.Add(as2_3); PathGeometry g1 = new PathGeometry();
g1.Figures.Add(pf);
#endregion #region 绘制矩形边框
RectangleGeometry g2 = new RectangleGeometry(new Rect(0, this.TailHeight, this.ActualWidth, this.ActualHeight - this.TailHeight)
, CornerRadius.TopLeft, CornerRadius.BottomRight);
#endregion #region 合并
result.Geometry1 = g1;
result.Geometry2 = g2;
result.GeometryCombineMode = GeometryCombineMode.Union;
#endregion return result;
} private Geometry CreateGeometryTailAtBottom()
{
CombinedGeometry result = new CombinedGeometry(); switch (this.Placement)
{
case EnumPlacement.BottomLeft:
break;
case EnumPlacement.BottomCenter:
this.TailHorizontalOffset = (this.ActualWidth - this.TailWidth) / 2;
break;
case EnumPlacement.BottomRight:
this.TailHorizontalOffset = this.ActualWidth - this.TailWidth - this.TailHorizontalOffset;
break;
} #region 绘制三角形
Point anglePoint1 = new Point(this.TailHorizontalOffset, this.ActualHeight - this.TailHeight);
Point anglePoint2 = new Point(this.TailHorizontalOffset + this.TailWidth / 2, this.ActualHeight);
Point anglePoint3 = new Point(this.TailHorizontalOffset + this.TailWidth, this.ActualHeight - this.TailHeight); LineSegment as1_2 = new LineSegment(anglePoint2, true);
LineSegment as2_3 = new LineSegment(anglePoint3, true); PathFigure pf = new PathFigure();
pf.IsClosed = false;
pf.StartPoint = anglePoint1;
pf.Segments.Add(as1_2);
pf.Segments.Add(as2_3); PathGeometry g1 = new PathGeometry();
g1.Figures.Add(pf);
#endregion #region 绘制矩形边框
RectangleGeometry g2 = new RectangleGeometry(new Rect(0, 0, this.ActualWidth, this.ActualHeight - this.TailHeight)
, CornerRadius.TopLeft, CornerRadius.BottomRight);
#endregion #region 合并
result.Geometry1 = g1;
result.Geometry2 = g2;
result.GeometryCombineMode = GeometryCombineMode.Union;
#endregion return result;
} private Brush CreateFillBrush()
{
Brush result = null; GradientStopCollection gsc = new GradientStopCollection();
gsc.Add(new GradientStop(((SolidColorBrush)this.Background).Color, 0));
LinearGradientBrush backGroundBrush = new LinearGradientBrush(gsc, new Point(0, 0), new Point(0, 1));
result = backGroundBrush; return result;
} /// <summary>
/// 根据三角形方向设置消息框的水平位置,偏左还是偏右
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
public static void OnDirectionPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var self = d as AngleBorder;
self.HorizontalAlignment = ((EnumPlacement)e.NewValue == EnumPlacement.RightCenter) ?
HorizontalAlignment.Right : HorizontalAlignment.Left;
}
#endregion
}
}
result.GeometryCombineMode=GeometryCombineMode.Union;
效果图:
源码下载:
WPF进阶教程 - 使用Decorator自定义带三角形的边框的更多相关文章
- .NET5 WPF进阶教程
一.概要 本系列将继<.net wpf快速入门教程>带领大家了解wpf,帮助各位在初级向中级过渡的中掌握基本该具备的能力.本系列视频长度大约在15分钟到30分钟左右,视频内容不仅仅会讲解技 ...
- ABP进阶教程8 - 自定义按钮
点这里进入ABP进阶教程目录 在功能按钮区增加一个自定义按钮 - Add(创建课程) 添加按钮 打开展示层(即JD.CRS.Web.Mvc)的\wwwroot\view-resources\Views ...
- SpringBoot进阶教程(六十五)自定义注解
在上一篇文章<SpringBoot进阶教程(六十四)注解大全>中介绍了springboot的常用注解,springboot提供的注解非常的多,这些注解简化了我们的很多操作.今天主要介绍介绍 ...
- 我的Android进阶之旅------>Android自定义View实现带数字的进度条(NumberProgressBar)
今天在Github上面看到一个来自于 daimajia所写的关于Android自定义View实现带数字的进度条(NumberProgressBar)的精彩案例,在这里分享给大家一起来学习学习!同时感谢 ...
- Modelbuilder进阶教程
Modelbuilder进阶教程 By 李远祥 Modelbuilder 进阶1 自定义变量 参数是用来交互操作的,因此,参数具备非常大的灵活性,包括参数的定义和调用. 除了工具里面的参数之外,还可以 ...
- shell进阶教程
背景:就自己常用的shell脚本写作风格,总结了一些知识点.也是作为交接工作的一部分文档.部分内容单独写 #!/bin/sh # shell脚本进阶教程 # 1.常用知识点:变量设置/日期设置/格式化 ...
- ABP进阶教程7 - 功能按钮
点这里进入ABP进阶教程目录 下载插件 打开Datatables官网(https://datatables.net/download/) 勾选Extensions/Buttons,下载插件,复制到JD ...
- WPF入门教程系列三——Application介绍(续)
接上文WPF入门教程系列二——Application介绍,我们继续来学习Application 三.WPF应用程序的关闭 WPF应用程序的关闭只有在应用程序的 Shutdown 方法被调用时,应用程序 ...
- 自定义带图片和文字的ImageTextButton
今天我们来讲一下有关自定义控件的问题,今天讲的这篇是从布局自定义开始的,难度不大,一看就明白,估计有的同学或者开发者看了说,这种方式多此一举,但是小编我不这么认为,多一种解决方式,就多一种举一反三的学 ...
随机推荐
- 【solr基础教程之九】客户端 分类: H4_SOLR/LUCENCE 2014-07-30 15:28 904人阅读 评论(0) 收藏
一.Java Script 1.由于Solr本身可以返回Json格式的结果,而JavaScript对于处理Json数据具有天然的优势,因此使用JavaScript实现Solr客户端是一个很好的选择. ...
- Access Violations 访问冲突(AVs)是Windows编程时发生的最麻烦的错误?
Access Violations<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office&qu ...
- Windows Phone 8.1 应用间共享
(1)LaunchUriAsync 将简单数据包含在 Uri 中,然后发送到目标应用: await Launcher.LaunchUriAsync(new Uri("target:messa ...
- android studio怎么添加.so文件?android studio加载so文件的方法
android studio 中 添加.so 文件,Android Studio中添加.jar文件和.so文件无疑是一件很重要也是很头疼的问题! 1.在src/main中添加 jniLibs文件夹 , ...
- javaScript_with用法
with语句用途 暂时改变作用域链.简化代码. 语法结构 with(object){ //其他语句 } 例1 with(person){ name= "zhang"; addres ...
- Momentum(动量/冲量)的理解及应用
1. 基本概念(Momentum vs SGD) Momentum 用于加速 SGD(随机梯度下降)在某一方向上的搜索以及抑制震荡的发生. GD(gradient descent) θt=θt−1−η ...
- RESET MASTER 和RESET SLAVE 命令的使用方法 注意事项
RESET MASTER 删除所有index file 中记录的所有binlog 文件,将日志索引文件清空,创建一个新的日志文件,这个命令通常仅仅用于第一次用于搭建主从关系的时的主库,注意 rese ...
- Git提交到多个远程仓库(多看两个文档)
Git提交到多个远程仓库(多看两个文档) 一.总结 一句话总结: 二. Git提交到多个远程仓库(多看两个文档) 有两种做法,先看第一种 一.通过命令行进行操作 例如我有下面两个仓库: Mybatis ...
- iOS 下载功能:断点下载(暂停和开始)(NSURLConnectionDataDelegate方法)
1,model文件代码 文件名称:HMFileDownloader.h #import <Foundation/Foundation.h> @interface HMFileDownloa ...
- Swagger与postman使用心得
Swagger接口文档,在线自动生成模板和页面.服务器地址加上swagger-ui.html后缀即可访问到(https://域名:端口号/swagger-ui.html). 使用时在java代码中引用 ...