Files
aistudio-wpf-diagram/AIStudio.Wpf.DiagramDesigner.Additionals/Controls/WPFRuler.cs
2023-02-11 22:20:24 +08:00

619 lines
26 KiB
C#

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
namespace AIStudio.Wpf.DiagramDesigner.Additionals.Controls
{
/// <summary>
/// A ruler control which supports both centimeters and inches. In order to use it vertically, change the <see cref="Marks">Marks</see> property to <c>Up</c> and rotate it ninety degrees.
/// </summary>
/// <remarks>
/// Contributions from <see
/// cref="http://www.orbifold.net/default/?p=2295&amp;cpage=1#comment-61500">Raf
/// Lenfers</see>
/// </remarks>
public class Ruler : FrameworkElement
{
#region Fields
private double SegmentHeight;
private readonly Pen p = new Pen(Brushes.Black, 1.0);
private readonly Pen ThinPen = new Pen(Brushes.Black, 0.5);
private readonly Pen BorderPen = new Pen(Brushes.Gray, 1.0);
private readonly Pen RedPen = new Pen(Brushes.Red, 2.0);
#endregion
#region Properties
#region Background
public Brush Background
{
get
{
return (Brush)GetValue(BackgroundProperty);
}
set
{
SetValue(BackgroundProperty, value);
}
}
/// <summary>
/// Identifies the Length dependency property.
/// </summary>
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.Register(
"Background",
typeof(Brush),
typeof(Ruler),
new FrameworkPropertyMetadata(new SolidColorBrush(Colors.White), FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#region Length
/// <summary>
/// Gets or sets the length of the ruler. If the <see cref="AutoSize"/> property is set to false (default) this
/// is a fixed length. Otherwise the length is calculated based on the actual width of the ruler.
/// </summary>
public double Length
{
get
{
if (this.AutoSize)
{
return (double)(Unit == Unit.Cm ? ScreenHelper.WidthToCm(this.ActualWidth) : ScreenHelper.WidthToInch(this.ActualWidth)) / this.Zoom;
}
else
{
return (double)GetValue(LengthProperty);
}
}
set
{
SetValue(LengthProperty, value);
}
}
/// <summary>
/// Identifies the Length dependency property.
/// </summary>
public static readonly DependencyProperty LengthProperty =
DependencyProperty.Register(
"Length",
typeof(double),
typeof(Ruler),
new FrameworkPropertyMetadata(20D, FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#region AutoSize
/// <summary>
/// Gets or sets the AutoSize behavior of the ruler.
/// false (default): the lenght of the ruler results from the <see cref="Length"/> property. If the window size is changed, e.g. wider
/// than the rulers length, free space is shown at the end of the ruler. No rescaling is done.
/// true : the length of the ruler is always adjusted to its actual width. This ensures that the ruler is shown
/// for the actual width of the window.
/// </summary>
public bool AutoSize
{
get
{
return (bool)GetValue(AutoSizeProperty);
}
set
{
SetValue(AutoSizeProperty, value);
this.InvalidateVisual();
}
}
/// <summary>
/// Identifies the AutoSize dependency property.
/// </summary>
public static readonly DependencyProperty AutoSizeProperty =
DependencyProperty.Register(
"AutoSize",
typeof(bool),
typeof(Ruler),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#region Zoom
/// <summary>
/// Gets or sets the zoom factor for the ruler. The default value is 1.0.
/// </summary>
public double Zoom
{
get
{
return (double)GetValue(ZoomProperty);
}
set
{
SetValue(ZoomProperty, value);
this.InvalidateVisual();
}
}
/// <summary>
/// Identifies the Zoom dependency property.
/// </summary>
public static readonly DependencyProperty ZoomProperty =
DependencyProperty.Register("Zoom", typeof(double), typeof(Ruler),
new FrameworkPropertyMetadata((double)1.0,
FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#region Chip
/// <summary>
/// Chip Dependency Property
/// </summary>
public static readonly DependencyProperty ChipProperty =
DependencyProperty.Register("Chip", typeof(double), typeof(Ruler),
new FrameworkPropertyMetadata((double)-1000,
FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// Sets the location of the chip in the units of the ruler.
/// So, to set the chip to 10 in cm units the chip needs to be set to 10.
/// Use the <see cref="DipHelper"/> class for conversions.
/// </summary>
public double Chip
{
get { return (double)GetValue(ChipProperty); }
set { SetValue(ChipProperty, value); }
}
#endregion
#region CountShift
/// <summary>
/// CountShift Dependency Property
/// </summary>
public static readonly DependencyProperty CountShiftProperty =
DependencyProperty.Register("CountShift", typeof(double), typeof(Ruler),
new FrameworkPropertyMetadata(0d,
FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// By default the counting of inches or cm starts at zero, this property allows you to shift
/// the counting.
/// </summary>
public double CountShift
{
get { return (double)GetValue(CountShiftProperty); }
set { SetValue(CountShiftProperty, value); }
}
#endregion
#region Marks
/// <summary>
/// Marks Dependency Property
/// </summary>
public static readonly DependencyProperty MarksProperty =
DependencyProperty.Register("Marks", typeof(MarksLocation), typeof(Ruler),
new FrameworkPropertyMetadata(MarksLocation.Up,
FrameworkPropertyMetadataOptions.AffectsRender));
/// <summary>
/// Gets or sets where the marks are shown in the ruler.
/// </summary>
public MarksLocation Marks
{
get { return (MarksLocation)GetValue(MarksProperty); }
set { SetValue(MarksProperty, value); }
}
#endregion
#region Unit
/// <summary>
/// Gets or sets the unit of the ruler.
/// Default value is Unit.Cm.
/// </summary>
public Unit Unit
{
get { return (Unit)GetValue(UnitProperty); }
set { SetValue(UnitProperty, value); }
}
/// <summary>
/// Identifies the Unit dependency property.
/// </summary>
public static readonly DependencyProperty UnitProperty =
DependencyProperty.Register(
"Unit",
typeof(Unit),
typeof(Ruler),
new FrameworkPropertyMetadata(Unit.Cm, FrameworkPropertyMetadataOptions.AffectsRender));
#endregion
#endregion
#region Constructor
static Ruler()
{
HeightProperty.OverrideMetadata(typeof(Ruler), new FrameworkPropertyMetadata(20.0));
}
public Ruler()
{
SegmentHeight = this.Height - 10;
}
#endregion
#region Methods
/// <summary>
/// Participates in rendering operations.
/// </summary>
/// <param name="drawingContext">The drawing instructions for a specific element. This context is provided to the layout system.</param>
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
var rect = new Rect(0, 0, RenderSize.Width, RenderSize.Height);
drawingContext.DrawRectangle(Background, null, rect);
double xDest = (Unit == Unit.Cm ? ScreenHelper.CmToWidth(Length) : ScreenHelper.InchToWidth(Length)) * this.Zoom;
drawingContext.DrawRectangle(null, BorderPen, new Rect(new Point(0.0, 0.0), new Point(xDest, Height)));
double chip = Unit == Unit.Cm ? ScreenHelper.CmToWidth(Chip) : ScreenHelper.InchToWidth(Chip);
drawingContext.DrawLine(RedPen, new Point(chip, 0), new Point(chip, Height));
//画偏移位置之前的
if (CountShift < 0)
{
for (double dUnit = -CountShift; dUnit > -1; dUnit--)
{
double d;
if (Unit == Unit.Cm)
{
d = ScreenHelper.CmToWidth(dUnit) * this.Zoom;
if (dUnit < Length)
{
for (int i = 1; i <= 9; i++)
{
if (i != 5)
{
double dMm = ScreenHelper.CmToWidth(dUnit + 0.1 * i) * this.Zoom;
if (Marks == MarksLocation.Up)
drawingContext.DrawLine(ThinPen, new Point(dMm, 0), new Point(dMm, SegmentHeight / 3.0));
else
drawingContext.DrawLine(ThinPen, new Point(dMm, Height), new Point(dMm, Height - SegmentHeight / 3.0));
}
}
double dMiddle = ScreenHelper.CmToWidth(dUnit + 0.5) * this.Zoom;
if (Marks == MarksLocation.Up)
drawingContext.DrawLine(p, new Point(dMiddle, 0), new Point(dMiddle, SegmentHeight * 2.0 / 3.0));
else
drawingContext.DrawLine(p, new Point(dMiddle, Height), new Point(dMiddle, Height - SegmentHeight * 2.0 / 3.0));
}
}
else
{
d = ScreenHelper.InchToWidth(dUnit) * this.Zoom;
if (dUnit < Length)
{
if (Marks == MarksLocation.Up)
{
double dQuarter = ScreenHelper.InchToWidth(dUnit + 0.25) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(dQuarter, 0),
new Point(dQuarter, SegmentHeight / 3.0));
double dMiddle = ScreenHelper.InchToWidth(dUnit + 0.5) * this.Zoom;
drawingContext.DrawLine(p, new Point(dMiddle, 0),
new Point(dMiddle, SegmentHeight * 2D / 3D));
double d3Quarter = ScreenHelper.InchToWidth(dUnit + 0.75) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(d3Quarter, 0),
new Point(d3Quarter, SegmentHeight / 3.0));
}
else
{
double dQuarter = ScreenHelper.InchToWidth(dUnit + 0.25) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(dQuarter, Height),
new Point(dQuarter, Height - SegmentHeight / 3.0));
double dMiddle = ScreenHelper.InchToWidth(dUnit + 0.5) * this.Zoom;
drawingContext.DrawLine(p, new Point(dMiddle, Height),
new Point(dMiddle, Height - SegmentHeight * 2D / 3D));
double d3Quarter = ScreenHelper.InchToWidth(dUnit + 0.75) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(d3Quarter, Height),
new Point(d3Quarter, Height - SegmentHeight / 3.0));
}
}
}
if (Marks == MarksLocation.Up)
drawingContext.DrawLine(p, new Point(d, 0), new Point(d, SegmentHeight));
else
drawingContext.DrawLine(p, new Point(d, Height), new Point(d, Height - SegmentHeight));
if ((dUnit != 0.0) && (dUnit < Length))
{
FormattedText ft = new FormattedText(
(Math.Round(dUnit + CountShift,0)).ToString(CultureInfo.CurrentCulture),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Arial"),
9,
Brushes.DimGray);
ft.SetFontWeight(FontWeights.Regular);
ft.TextAlignment = TextAlignment.Center;
if (Marks == MarksLocation.Up)
drawingContext.DrawText(ft, new Point(d, Height - ft.Height));
else
drawingContext.DrawText(ft, new Point(d, Height - SegmentHeight - ft.Height));
}
}
}
//画偏移位置之后的
for (double dUnit = -CountShift; dUnit <= Length; dUnit++)
{
double d;
if (Unit == Unit.Cm)
{
d = ScreenHelper.CmToWidth(dUnit) * this.Zoom;
if (dUnit < Length)
{
for (int i = 1; i <= 9; i++)
{
if (i != 5)
{
double dMm = ScreenHelper.CmToWidth(dUnit + 0.1 * i) * this.Zoom;
if (Marks == MarksLocation.Up)
drawingContext.DrawLine(ThinPen, new Point(dMm, 0), new Point(dMm, SegmentHeight / 3.0));
else
drawingContext.DrawLine(ThinPen, new Point(dMm, Height), new Point(dMm, Height - SegmentHeight / 3.0));
}
}
double dMiddle = ScreenHelper.CmToWidth(dUnit + 0.5) * this.Zoom;
if (Marks == MarksLocation.Up)
drawingContext.DrawLine(p, new Point(dMiddle, 0), new Point(dMiddle, SegmentHeight * 2.0 / 3.0));
else
drawingContext.DrawLine(p, new Point(dMiddle, Height), new Point(dMiddle, Height - SegmentHeight * 2.0 / 3.0));
}
}
else
{
d = ScreenHelper.InchToWidth(dUnit) * this.Zoom;
if (dUnit < Length)
{
if (Marks == MarksLocation.Up)
{
double dQuarter = ScreenHelper.InchToWidth(dUnit + 0.25) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(dQuarter, 0),
new Point(dQuarter, SegmentHeight / 3.0));
double dMiddle = ScreenHelper.InchToWidth(dUnit + 0.5) * this.Zoom;
drawingContext.DrawLine(p, new Point(dMiddle, 0),
new Point(dMiddle, SegmentHeight * 2D / 3D));
double d3Quarter = ScreenHelper.InchToWidth(dUnit + 0.75) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(d3Quarter, 0),
new Point(d3Quarter, SegmentHeight / 3.0));
}
else
{
double dQuarter = ScreenHelper.InchToWidth(dUnit + 0.25) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(dQuarter, Height),
new Point(dQuarter, Height - SegmentHeight / 3.0));
double dMiddle = ScreenHelper.InchToWidth(dUnit + 0.5) * this.Zoom;
drawingContext.DrawLine(p, new Point(dMiddle, Height),
new Point(dMiddle, Height - SegmentHeight * 2D / 3D));
double d3Quarter = ScreenHelper.InchToWidth(dUnit + 0.75) * this.Zoom;
drawingContext.DrawLine(ThinPen, new Point(d3Quarter, Height),
new Point(d3Quarter, Height - SegmentHeight / 3.0));
}
}
}
if (Marks == MarksLocation.Up)
drawingContext.DrawLine(p, new Point(d, 0), new Point(d, SegmentHeight));
else
drawingContext.DrawLine(p, new Point(d, Height), new Point(d, Height - SegmentHeight));
if ((dUnit != 0.0) && (dUnit < Length))
{
FormattedText ft = new FormattedText(
(Math.Round(dUnit + CountShift,0)).ToString(CultureInfo.CurrentCulture),
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Arial"),
9,
Brushes.DimGray);
ft.SetFontWeight(FontWeights.Regular);
ft.TextAlignment = TextAlignment.Center;
if (Marks == MarksLocation.Up)
drawingContext.DrawText(ft, new Point(d, Height - ft.Height));
else
drawingContext.DrawText(ft, new Point(d, Height - SegmentHeight - ft.Height));
}
}
}
/// <summary>
/// Measures an instance during the first layout pass prior to arranging it.
/// </summary>
/// <param name="availableSize">A maximum Size to not exceed.</param>
/// <returns>The maximum Size for the instance.</returns>
protected override Size MeasureOverride(Size availableSize)
{
return base.MeasureOverride(availableSize);
//Size desiredSize;
//if (Unit == Unit.Cm)
//{
// desiredSize = new Size(ScreenHelper.CmToWidth(Length), Height);
//}
//else
//{
// desiredSize = new Size(ScreenHelper.InchToWidth(Length), Height);
//}
//return desiredSize;
}
#endregion
}
/// <summary>
/// The unit type of the ruler.
/// </summary>
public enum Unit
{
/// <summary>
/// the unit is Centimeter.
/// </summary>
Cm,
/// <summary>
/// The unit is Inch.
/// </summary>
Inch
};
public enum MarksLocation
{
Up, Down
}
///// <summary>
///// A helper class for DIP (Device Independent Pixels) conversion and scaling operations.
///// </summary>
//public static class DipHelper
//{
// /// <summary>
// /// Converts millimeters to DIP (Device Independant Pixels).
// /// </summary>
// /// <param name="mm">A millimeter value.</param>
// /// <returns>A DIP value.</returns>
// public static double MmToDip(double mm)
// {
// return CmToDip(mm / 10.0);
// }
// /// <summary>
// /// Converts centimeters to DIP (Device Independant Pixels).
// /// </summary>
// /// <param name="cm">A centimeter value.</param>
// /// <returns>A DIP value.</returns>
// public static double CmToDip(double cm)
// {
// return (cm * 96.0 / 2.54);
// }
// /// <summary>
// /// Converts inches to DIP (Device Independant Pixels).
// /// </summary>
// /// <param name="inch">An inch value.</param>
// /// <returns>A DIP value.</returns>
// public static double InchToDip(double inch)
// {
// return (inch * 96.0);
// }
// public static double DipToInch(double dip)
// {
// return dip / 96D;
// }
// /// <summary>
// /// Converts font points to DIP (Device Independant Pixels).
// /// </summary>
// /// <param name="pt">A font point value.</param>
// /// <returns>A DIP value.</returns>
// public static double PtToDip(double pt)
// {
// return (pt * 96.0 / 72.0);
// }
// /// <summary>
// /// Converts DIP (Device Independant Pixels) to centimeters.
// /// </summary>
// /// <param name="dip">A DIP value.</param>
// /// <returns>A centimeter value.</returns>
// public static double DipToCm(double dip)
// {
// return (dip * 2.54 / 96.0);
// }
// /// <summary>
// /// Converts DIP (Device Independant Pixels) to millimeters.
// /// </summary>
// /// <param name="dip">A DIP value.</param>
// /// <returns>A millimeter value.</returns>
// public static double DipToMm(double dip)
// {
// return DipToCm(dip) * 10.0;
// }
// /// <summary>
// /// Gets the system DPI scale factor (compared to 96 dpi).
// /// From http://blogs.msdn.com/jaimer/archive/2007/03/07/getting-system-dpi-in-wpf-app.aspx
// /// Should not be called before the Loaded event (else XamlException mat throw)
// /// </summary>
// /// <returns>A Point object containing the X- and Y- scale factor.</returns>
// private static Point GetSystemDpiFactor()
// {
// PresentationSource source = PresentationSource.FromVisual(Application.Current.MainWindow);
// Matrix m = source.CompositionTarget.TransformToDevice;
// return new Point(m.M11, m.M22);
// }
// private const double DpiBase = 96.0;
// /// <summary>
// /// Gets the system configured DPI.
// /// </summary>
// /// <returns>A Point object containing the X- and Y- DPI.</returns>
// public static Point GetSystemDpi()
// {
// Point sysDpiFactor = GetSystemDpiFactor();
// return new Point(
// sysDpiFactor.X * DpiBase,
// sysDpiFactor.Y * DpiBase);
// }
// /// <summary>
// /// Gets the physical pixel density (DPI) of the screen.
// /// </summary>
// /// <param name="diagonalScreenSize">Size - in inch - of the diagonal of the screen.</param>
// /// <returns>A Point object containing the X- and Y- DPI.</returns>
// public static Point GetPhysicalDpi(double diagonalScreenSize)
// {
// Point sysDpiFactor = GetSystemDpiFactor();
// double pixelScreenWidth = SystemParameters.PrimaryScreenWidth * sysDpiFactor.X;
// double pixelScreenHeight = SystemParameters.PrimaryScreenHeight * sysDpiFactor.Y;
// double formatRate = pixelScreenWidth / pixelScreenHeight;
// double inchHeight = diagonalScreenSize / Math.Sqrt(formatRate * formatRate + 1.0);
// double inchWidth = formatRate * inchHeight;
// double xDpi = Math.Round(pixelScreenWidth / inchWidth);
// double yDpi = Math.Round(pixelScreenHeight / inchHeight);
// return new Point(xDpi, yDpi);
// }
// /// <summary>
// /// Converts a DPI into a scale factor (compared to system DPI).
// /// </summary>
// /// <param name="dpi">A Point object containing the X- and Y- DPI to convert.</param>
// /// <returns>A Point object containing the X- and Y- scale factor.</returns>
// public static Point DpiToScaleFactor(Point dpi)
// {
// Point sysDpi = GetSystemDpi();
// return new Point(
// dpi.X / sysDpi.X,
// dpi.Y / sysDpi.Y);
// }
// /// <summary>
// /// Gets the scale factor to apply to a WPF application
// /// so that 96 DIP always equals 1 inch on the screen (whatever the system DPI).
// /// </summary>
// /// <param name="diagonalScreenSize">Size - in inch - of the diagonal of the screen</param>
// /// <returns>A Point object containing the X- and Y- scale factor.</returns>
// public static Point GetScreenIndependentScaleFactor(double diagonalScreenSize)
// {
// return DpiToScaleFactor(GetPhysicalDpi(diagonalScreenSize));
// }
//}
}