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 ? ScreenHelper.WidthToCm(this.ActualWidth) : ScreenHelper.WidthToInch(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 ? 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)); } } } /// /// 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(ScreenHelper.CmToWidth(Length), Height); //} //else //{ // desiredSize = new Size(ScreenHelper.InchToWidth(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)); // } //} }