2021-07-23 09:42:22 +08:00
using System ;
using System.Globalization ;
using System.Windows ;
using System.Windows.Media ;
2022-10-28 22:45:39 +08:00
namespace AIStudio.Wpf.DiagramHelper.Controls
2021-07-23 09:42:22 +08:00
{
/// <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&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 ? DipHelper . DipToCm ( this . ActualWidth ) : DipHelper . DipToInch ( 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 ? DipHelper . CmToDip ( Length ) : DipHelper . InchToDip ( Length ) ) * this . Zoom ;
drawingContext . DrawRectangle ( null , BorderPen , new Rect ( new Point ( 0.0 , 0.0 ) , new Point ( xDest , Height ) ) ) ;
double chip = Unit = = Unit . Cm ? DipHelper . CmToDip ( Chip ) : DipHelper . InchToDip ( 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 = DipHelper . CmToDip ( dUnit ) * this . Zoom ;
if ( dUnit < Length )
{
for ( int i = 1 ; i < = 9 ; i + + )
{
if ( i ! = 5 )
{
double dMm = DipHelper . CmToDip ( 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 = DipHelper . CmToDip ( 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 = DipHelper . InchToDip ( dUnit ) * this . Zoom ;
if ( dUnit < Length )
{
if ( Marks = = MarksLocation . Up )
{
double dQuarter = DipHelper . InchToDip ( dUnit + 0.25 ) * this . Zoom ;
drawingContext . DrawLine ( ThinPen , new Point ( dQuarter , 0 ) ,
new Point ( dQuarter , SegmentHeight / 3.0 ) ) ;
double dMiddle = DipHelper . InchToDip ( dUnit + 0.5 ) * this . Zoom ;
drawingContext . DrawLine ( p , new Point ( dMiddle , 0 ) ,
new Point ( dMiddle , SegmentHeight * 2D / 3D ) ) ;
double d3Quarter = DipHelper . InchToDip ( dUnit + 0.75 ) * this . Zoom ;
drawingContext . DrawLine ( ThinPen , new Point ( d3Quarter , 0 ) ,
new Point ( d3Quarter , SegmentHeight / 3.0 ) ) ;
}
else
{
double dQuarter = DipHelper . InchToDip ( dUnit + 0.25 ) * this . Zoom ;
drawingContext . DrawLine ( ThinPen , new Point ( dQuarter , Height ) ,
new Point ( dQuarter , Height - SegmentHeight / 3.0 ) ) ;
double dMiddle = DipHelper . InchToDip ( dUnit + 0.5 ) * this . Zoom ;
drawingContext . DrawLine ( p , new Point ( dMiddle , Height ) ,
new Point ( dMiddle , Height - SegmentHeight * 2D / 3D ) ) ;
double d3Quarter = DipHelper . InchToDip ( 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 (
2021-07-23 16:46:22 +08:00
( Math . Round ( dUnit + CountShift , 0 ) ) . ToString ( CultureInfo . CurrentCulture ) ,
2021-07-23 09:42:22 +08:00
CultureInfo . CurrentCulture ,
FlowDirection . LeftToRight ,
new Typeface ( "Arial" ) ,
DipHelper . PtToDip ( 6 ) ,
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 = DipHelper . CmToDip ( dUnit ) * this . Zoom ;
if ( dUnit < Length )
{
for ( int i = 1 ; i < = 9 ; i + + )
{
if ( i ! = 5 )
{
double dMm = DipHelper . CmToDip ( 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 = DipHelper . CmToDip ( 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 = DipHelper . InchToDip ( dUnit ) * this . Zoom ;
if ( dUnit < Length )
{
if ( Marks = = MarksLocation . Up )
{
double dQuarter = DipHelper . InchToDip ( dUnit + 0.25 ) * this . Zoom ;
drawingContext . DrawLine ( ThinPen , new Point ( dQuarter , 0 ) ,
new Point ( dQuarter , SegmentHeight / 3.0 ) ) ;
double dMiddle = DipHelper . InchToDip ( dUnit + 0.5 ) * this . Zoom ;
drawingContext . DrawLine ( p , new Point ( dMiddle , 0 ) ,
new Point ( dMiddle , SegmentHeight * 2D / 3D ) ) ;
double d3Quarter = DipHelper . InchToDip ( dUnit + 0.75 ) * this . Zoom ;
drawingContext . DrawLine ( ThinPen , new Point ( d3Quarter , 0 ) ,
new Point ( d3Quarter , SegmentHeight / 3.0 ) ) ;
}
else
{
double dQuarter = DipHelper . InchToDip ( dUnit + 0.25 ) * this . Zoom ;
drawingContext . DrawLine ( ThinPen , new Point ( dQuarter , Height ) ,
new Point ( dQuarter , Height - SegmentHeight / 3.0 ) ) ;
double dMiddle = DipHelper . InchToDip ( dUnit + 0.5 ) * this . Zoom ;
drawingContext . DrawLine ( p , new Point ( dMiddle , Height ) ,
new Point ( dMiddle , Height - SegmentHeight * 2D / 3D ) ) ;
double d3Quarter = DipHelper . InchToDip ( 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 (
2021-07-23 16:46:22 +08:00
( Math . Round ( dUnit + CountShift , 0 ) ) . ToString ( CultureInfo . CurrentCulture ) ,
2021-07-23 09:42:22 +08:00
CultureInfo . CurrentCulture ,
FlowDirection . LeftToRight ,
new Typeface ( "Arial" ) ,
DipHelper . PtToDip ( 6 ) ,
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(DipHelper.CmToDip(Length), Height);
//}
//else
//{
// desiredSize = new Size(DipHelper.InchToDip(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 ) ) ;
}
}
}