Wpf_AiSportsMicrospace/Wpf_AiSportsMicrospace/MyUserControl/ProgressToRectangleGeometryConverter.cs
2025-09-26 16:46:30 +08:00

186 lines
7.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Ink;
using System.Windows.Media;
namespace Wpf_AiSportsMicrospace.MyUserControl
{
/// <summary>
/// 将进度值转换为表示矩形边框部分路径的几何图形(StreamGeometry)的转换器。
/// 该转换器用于绘制一个带圆角的矩形边框,并根据进度值绘制其部分轮廓线,常用于进度条等UI控件中。
/// </summary>
public class ProgressToRectangleGeometryConverter : IValueConverter
{
/// <summary>
/// 将进度值转换为表示矩形边框部分路径的几何图形(StreamGeometry)对象
/// </summary>
/// <param name="value">当前进度值,应为 [0.0, 1.0] 范围内的 double 类型</param>
/// <param name="targetType">目标类型,应为 Geometry 类型</param>
/// <param name="parameter">参数字符串,格式为 "width,height,radius",分别表示矩形的宽度、高度和圆角半径</param>
/// <param name="culture">当前区域性信息</param>
/// <returns>返回一个 StreamGeometry 对象,表示根据进度绘制的部分矩形边框</returns>
// parameter: "width,height,radius"
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double progress = (double)value;
string[] sizes = parameter.ToString()!.Split(',');
double width = double.Parse(sizes[0]);
double height = double.Parse(sizes[1]);
double radius = double.Parse(sizes[2]);
double stroke = sizes.Length > 3 ? double.Parse(sizes[3]) : 0;
double inset = stroke / 2.0;
double x = inset;
double y = inset;
double w = width - stroke;
double h = height - stroke;
// 保持原始圆角半径
double r = radius;
// 计算总周长
double perimeter = 2 * (w + h - 4 * r) + 2 * Math.PI * r;
double length = perimeter * progress;
var geometry = new StreamGeometry();
using (var ctx = geometry.Open())
{
// 从左上角圆角起点,顺时针
ctx.BeginFigure(new Point(x + r, y), false, false);
// top line
double top = Math.Min(length, w - 2 * r);
ctx.LineTo(new Point(x + r + top, y), true, false);
length -= top;
if (length <= 0) return geometry;
// top-right arc
double arc = Math.Min(length, Math.PI / 2 * r);
if (arc > 0)
{
double angle = arc / r;
double endX = x + w - r + Math.Sin(angle) * r;
double endY = y + r - Math.Cos(angle) * r;
if (arc < Math.PI / 2 * r)
{
ctx.ArcTo(
new Point(endX, endY),
new Size(r, r),
0, false, SweepDirection.Clockwise, true, false);
return geometry;
}
else
{
ctx.ArcTo(
new Point(x + w, y + r),
new Size(r, r),
0, false, SweepDirection.Clockwise, true, false);
length -= Math.PI / 2 * r;
}
}
if (length <= 0) return geometry;
// right line
double right = Math.Min(length, h - 2 * r);
ctx.LineTo(new Point(x + w, y + r + right), true, false);
length -= right;
if (length <= 0) return geometry;
// bottom-right arc
arc = Math.Min(length, Math.PI / 2 * r);
if (arc > 0)
{
double angle = arc / r;
double endX = x + w - r + Math.Cos(angle) * r;
double endY = y + h - r + Math.Sin(angle) * r;
if (arc < Math.PI / 2 * r)
{
ctx.ArcTo(
new Point(endX, endY),
new Size(r, r),
0, false, SweepDirection.Clockwise, true, false);
return geometry;
}
else
{
ctx.ArcTo(
new Point(x + w - r, y + h),
new Size(r, r),
0, false, SweepDirection.Clockwise, true, false);
length -= Math.PI / 2 * r;
}
}
if (length <= 0) return geometry;
// bottom line
double bottom = Math.Min(length, w - 2 * r);
ctx.LineTo(new Point(x + w - r - bottom, y + h), true, false);
length -= bottom;
if (length <= 0) return geometry;
// bottom-left arc
arc = Math.Min(length, Math.PI / 2 * r);
if (arc > 0)
{
double angle = arc / r;
double endX = x + r - Math.Sin(angle) * r;
double endY = y + h - r + Math.Cos(angle) * r;
if (arc < Math.PI / 2 * r)
{
ctx.ArcTo(
new Point(endX, endY),
new Size(r, r),
0, false, SweepDirection.Clockwise, true, false);
return geometry;
}
else
{
ctx.ArcTo(
new Point(x, y + h - r),
new Size(r, r),
0, false, SweepDirection.Clockwise, true, false);
length -= Math.PI / 2 * r;
}
}
if (length <= 0) return geometry;
// left line
double left = Math.Min(length, h - 2 * r);
ctx.LineTo(new Point(x, y + h - r - left), true, false);
length -= left;
if (length <= 0) return geometry;
// top-left arc
arc = Math.Min(length, Math.PI / 2 * r);
if (arc > 0)
{
double angle = arc / r;
double endX = x + r - Math.Cos(angle) * r;
double endY = y + r - Math.Sin(angle) * r;
if (arc < Math.PI / 2 * r)
{
ctx.ArcTo(
new Point(endX, endY),
new Size(r, r),
0, false, SweepDirection.Clockwise, true, false);
return geometry;
}
else
{
ctx.ArcTo(
new Point(x + r, y),
new Size(r, r),
0, false, SweepDirection.Clockwise, true, false);
}
}
}
geometry.Freeze();
return geometry;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException();
}
}