using System; using System.Globalization; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace Tango.Editors { /// /// A ruler el which displays ruler in pixels. /// In order to use it vertically, change the Marks property to Up and rotate it ninety degrees. /// /// /// Rewritten by: Sebestyen Murancsik /// /// Contributions from Raf /// Lenfers /// http://visualizationtools.net/default/wpf-ruler/ /// internal class PixelRuler : Control { internal enum RulerOrientationEnum { Horizontal, Vertical } #region Fields private double SegmentHeight; private Pen p = new Pen(Brushes.Orange, 1.0); private Pen markerPen = new Pen(Brushes.Orange, 0.5); private Pen BorderPen = new Pen(Brushes.Orange, 1.0); private Pen RedPen = new Pen(Brushes.Red, 2.0); #endregion #region Properties public RulerOrientationEnum Orientation { get { return (RulerOrientationEnum)GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } } // Using a DependencyProperty as the backing store for Orientation. This enables animation, styling, binding, etc... public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(RulerOrientationEnum), typeof(PixelRuler), new PropertyMetadata(RulerOrientationEnum.Horizontal)); #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) { if (Orientation == RulerOrientationEnum.Horizontal) { return ActualWidth / Zoom; } else { return ActualHeight / 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(PixelRuler), 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(PixelRuler), 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(PixelRuler), new FrameworkPropertyMetadata((double)1.0, FrameworkPropertyMetadataOptions.AffectsRender)); #endregion #region SmallStep /// /// Gets or sets the small step for the ruler. The default value is 25.0. /// public double SmallStep { get { return (double)GetValue(SmallStepProperty); } set { SetValue(SmallStepProperty, value); } } /// /// Identifies the Zoom dependency property. /// public static readonly DependencyProperty SmallStepProperty = DependencyProperty.Register("SmallStep", typeof(double), typeof(PixelRuler), new FrameworkPropertyMetadata((double)25.0, FrameworkPropertyMetadataOptions.AffectsRender)); #endregion #region Step /// /// Gets or sets the step for the ruler. The default value is 100.0. /// public double Step { get { return (double)GetValue(StepProperty); } set { SetValue(StepProperty, value); } } /// /// Identifies the Zoom dependency property. /// public static readonly DependencyProperty StepProperty = DependencyProperty.Register("Step", typeof(double), typeof(PixelRuler), new FrameworkPropertyMetadata((double)100.0, FrameworkPropertyMetadataOptions.AffectsRender)); #endregion #region Chip /// /// Chip Dependency Property /// public static readonly DependencyProperty ChipProperty = DependencyProperty.Register("Chip", typeof(double), typeof(PixelRuler), new FrameworkPropertyMetadata((double)-1000, FrameworkPropertyMetadataOptions.AffectsRender)); /// /// Sets the location of the chip in the units of the ruler. /// So, to set the chip to 100px units the chip needs to be set to 100. /// 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(int), typeof(PixelRuler), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsRender)); /// /// By default the counting of numbers starts at zero, this property allows you to shift /// the counting. /// public int CountShift { get { return (int)GetValue(CountShiftProperty); } set { SetValue(CountShiftProperty, value); } } #endregion #region Marks /// /// Marks Dependency Property /// public static readonly DependencyProperty MarksProperty = DependencyProperty.Register("Marks", typeof(MarksLocation), typeof(PixelRuler), 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 #endregion #region Constructors public PixelRuler() { } #endregion #region Methods private void SetSteps() { var value = Zoom; if (value >= 4.8) { SmallStep = 5; Step = 10; } else if (value >= 4) { SmallStep = 5; Step = 10; } else if (value >= 3) { SmallStep = 10; Step = 20; } else if (value >= 2) { SmallStep = (12.5) / 2; Step = 25; } else if (value >= 1.5) { SmallStep = 12.5; Step = 50; } else if (value >= 1) { SmallStep = 25; Step = 100; } else if (value <= 0.2) { SmallStep = 100; Step = 400; } else if (value <= 0.5) { SmallStep = 50; Step = 200; } } /// /// 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); SetSteps(); var markersPen = new Pen(Foreground, 1); SegmentHeight = ActualHeight - 10; if (Orientation == RulerOrientationEnum.Horizontal) { double xDest = Length * Zoom; //drawingContext.DrawRectangle(RulerBackground, BorderPen, new Rect(new Point(0.0, 0.0), new Point(xDest, Height))); //drawingContext.DrawLine(RedPen, new Point(Chip, 0), new Point(Chip, Height)); for (double dUnit = 0; dUnit < Length; dUnit += SmallStep) { double d = dUnit * this.Zoom; var startHeight = ActualHeight; var endHeight = ((dUnit % Step == 0) ? (this.ActualHeight / 5) * 1.4 : this.ActualHeight / 5); if (Marks == MarksLocation.Down) { drawingContext.DrawLine(markersPen, new Point(d, startHeight), new Point(d, (startHeight - endHeight))); } else { drawingContext.DrawLine(markersPen, new Point(d, 0), new Point(d, endHeight)); } if ((dUnit != 0.0) && (dUnit % Step == 0) && (dUnit < Length)) { FormattedText ft = new FormattedText((dUnit + CountShift).ToString(CultureInfo.CurrentCulture), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface(FontFamily.ToString()), FontSize, Foreground); ft.SetFontWeight(FontWeight); ft.TextAlignment = TextAlignment.Center; //drawingContext.PushTransform(new RotateTransform(180)); if (Marks == MarksLocation.Down) { drawingContext.DrawText(ft, new Point(d, (ActualHeight / 2) - (ft.Height / 2) - 2)); } else { drawingContext.DrawText(ft, new Point(d, (ActualHeight / 2) - (ft.Height / 2) + 2)); } //drawingContext.PushTransform(new RotateTransform(0)); } } } else { double xDest = Length * Zoom; //drawingContext.DrawRectangle(RulerBackground, BorderPen, new Rect(new Point(0.0, 0.0), new Point(Width, xDest))); //drawingContext.DrawLine(RedPen, new Point(Chip, 0), new Point(Chip, ActualHeight)); for (double dUnit = 0; dUnit < Length; dUnit += SmallStep) { double d = dUnit * this.Zoom; double startHeight; double endHeight; if (Marks == MarksLocation.Up) { startHeight = 0; // Main step or small step? endHeight = ((dUnit % Step == 0) ? SegmentHeight : SegmentHeight / 2); } else { startHeight = Height; // Main step or small step? endHeight = ((dUnit % Step == 0) ? SegmentHeight : SegmentHeight * 1.5); } drawingContext.DrawLine(markersPen, new Point(21, d), new Point(endHeight + 8, d)); if ((dUnit != 0.0) && (dUnit % Step == 0) && (dUnit < Length)) { FormattedText ft = new FormattedText((dUnit + CountShift).ToString(CultureInfo.CurrentCulture), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface(FontFamily.ToString()), FontSize, Foreground); ft.SetFontWeight(FontWeight); ft.TextAlignment = TextAlignment.Center; drawingContext.DrawText(ft, new Point(d, (ActualHeight / 2) - (ft.Height / 2) - 2)); } } } } #endregion } internal enum MarksLocation { Up, Down } /// /// A helper class for DIP (Device Independent Pixels) conversion and scaling operations. /// internal static class DipHelper { /// /// 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); } /// /// 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); } } }