using System; using System.Globalization; using System.Windows; using System.Windows.Media; namespace AIStudio.Wpf.DiagramDesigner.Additionals.Controls { /// /// A ruler control which supports both centimeters and inches. In order to use it vertically, change the Marks property to Up and rotate it ninety degrees. /// /// /// Contributions from Raf /// Lenfers /// 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); } } /// /// Identifies the Length dependency property. /// public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register( "Background", typeof(Brush), typeof(Ruler), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.White), FrameworkPropertyMetadataOptions.AffectsRender)); #endregion #region Length /// /// Gets or sets the length of the ruler. If the property is set to false (default) this /// is a fixed length. Otherwise the length is calculated based on the actual width of the ruler. /// 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); } } /// /// Identifies the Length dependency property. /// public static readonly DependencyProperty LengthProperty = DependencyProperty.Register( "Length", typeof(double), typeof(Ruler), new FrameworkPropertyMetadata(20D, FrameworkPropertyMetadataOptions.AffectsRender)); #endregion #region AutoSize /// /// Gets or sets the AutoSize behavior of the ruler. /// false (default): the lenght of the ruler results from the 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. /// public bool AutoSize { get { return (bool)GetValue(AutoSizeProperty); } set { SetValue(AutoSizeProperty, value); this.InvalidateVisual(); } } /// /// Identifies the AutoSize dependency property. /// public static readonly DependencyProperty AutoSizeProperty = DependencyProperty.Register( "AutoSize", typeof(bool), typeof(Ruler), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.AffectsRender)); #endregion #region Zoom /// /// Gets or sets the zoom factor for the ruler. The default value is 1.0. /// public double Zoom { get { return (double)GetValue(ZoomProperty); } set { SetValue(ZoomProperty, value); this.InvalidateVisual(); } } /// /// Identifies the Zoom dependency property. /// public static readonly DependencyProperty ZoomProperty = DependencyProperty.Register("Zoom", typeof(double), typeof(Ruler), new FrameworkPropertyMetadata((double)1.0, FrameworkPropertyMetadataOptions.AffectsRender)); #endregion #region Chip /// /// Chip Dependency Property /// public static readonly DependencyProperty ChipProperty = DependencyProperty.Register("Chip", typeof(double), typeof(Ruler), new FrameworkPropertyMetadata((double)-1000, FrameworkPropertyMetadataOptions.AffectsRender)); /// /// 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 class for conversions. /// public double Chip { get { return (double)GetValue(ChipProperty); } set { SetValue(ChipProperty, value); } } #endregion #region CountShift /// /// CountShift Dependency Property /// public static readonly DependencyProperty CountShiftProperty = DependencyProperty.Register("CountShift", typeof(double), typeof(Ruler), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsRender)); /// /// By default the counting of inches or cm starts at zero, this property allows you to shift /// the counting. /// public double CountShift { get { return (double)GetValue(CountShiftProperty); } set { SetValue(CountShiftProperty, value); } } #endregion #region Marks /// /// Marks Dependency Property /// public static readonly DependencyProperty MarksProperty = DependencyProperty.Register("Marks", typeof(MarksLocation), typeof(Ruler), new FrameworkPropertyMetadata(MarksLocation.Up, FrameworkPropertyMetadataOptions.AffectsRender)); /// /// Gets or sets where the marks are shown in the ruler. /// public MarksLocation Marks { get { return (MarksLocation)GetValue(MarksProperty); } set { SetValue(MarksProperty, value); } } #endregion #region Unit /// /// Gets or sets the unit of the ruler. /// Default value is Unit.Cm. /// public Unit Unit { get { return (Unit)GetValue(UnitProperty); } set { SetValue(UnitProperty, value); } } /// /// Identifies the Unit dependency property. /// 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 /// /// Participates in rendering operations. /// /// The drawing instructions for a specific element. This context is provided to the layout system. 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( (Math.Round(dUnit + CountShift,0)).ToString(CultureInfo.CurrentCulture), 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( (Math.Round(dUnit + CountShift,0)).ToString(CultureInfo.CurrentCulture), 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)); } } } /// /// Measures an instance during the first layout pass prior to arranging it. /// /// A maximum Size to not exceed. /// The maximum Size for the instance. 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 } /// /// The unit type of the ruler. /// public enum Unit { /// /// the unit is Centimeter. /// Cm, /// /// The unit is Inch. /// Inch }; public enum MarksLocation { Up, Down } /// /// A helper class for DIP (Device Independent Pixels) conversion and scaling operations. /// public static class DipHelper { /// /// Converts millimeters to DIP (Device Independant Pixels). /// /// A millimeter value. /// A DIP value. public static double MmToDip(double mm) { return CmToDip(mm / 10.0); } /// /// Converts centimeters to DIP (Device Independant Pixels). /// /// A centimeter value. /// A DIP value. public static double CmToDip(double cm) { return (cm * 96.0 / 2.54); } /// /// Converts inches to DIP (Device Independant Pixels). /// /// An inch value. /// A DIP value. public static double InchToDip(double inch) { return (inch * 96.0); } public static double DipToInch(double dip) { return dip / 96D; } /// /// Converts font points to DIP (Device Independant Pixels). /// /// A font point value. /// A DIP value. public static double PtToDip(double pt) { return (pt * 96.0 / 72.0); } /// /// Converts DIP (Device Independant Pixels) to centimeters. /// /// A DIP value. /// A centimeter value. public static double DipToCm(double dip) { return (dip * 2.54 / 96.0); } /// /// Converts DIP (Device Independant Pixels) to millimeters. /// /// A DIP value. /// A millimeter value. public static double DipToMm(double dip) { return DipToCm(dip) * 10.0; } /// /// 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) /// /// A Point object containing the X- and Y- scale factor. 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; /// /// Gets the system configured DPI. /// /// A Point object containing the X- and Y- DPI. public static Point GetSystemDpi() { Point sysDpiFactor = GetSystemDpiFactor(); return new Point( sysDpiFactor.X * DpiBase, sysDpiFactor.Y * DpiBase); } /// /// Gets the physical pixel density (DPI) of the screen. /// /// Size - in inch - of the diagonal of the screen. /// A Point object containing the X- and Y- DPI. 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); } /// /// Converts a DPI into a scale factor (compared to system DPI). /// /// A Point object containing the X- and Y- DPI to convert. /// A Point object containing the X- and Y- scale factor. public static Point DpiToScaleFactor(Point dpi) { Point sysDpi = GetSystemDpi(); return new Point( dpi.X / sysDpi.X, dpi.Y / sysDpi.Y); } /// /// 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). /// /// Size - in inch - of the diagonal of the screen /// A Point object containing the X- and Y- scale factor. public static Point GetScreenIndependentScaleFactor(double diagonalScreenSize) { return DpiToScaleFactor(GetPhysicalDpi(diagonalScreenSize)); } } }