using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Markup; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using XCommon.Log; using XHandler.Class; namespace XHandler.Controls.DrawCanvas { /// /// 按照步骤 1a 或 1b 操作,然后执行步骤 2 以在 XAML 文件中使用此自定义控件。 /// /// 步骤 1a) 在当前项目中存在的 XAML 文件中使用该自定义控件。 /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根 /// 元素中: /// /// xmlns:MyNamespace="clr-namespace:XHandler.Controls" /// /// /// 步骤 1b) 在其他项目中存在的 XAML 文件中使用该自定义控件。 /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根 /// 元素中: /// /// xmlns:MyNamespace="clr-namespace:XHandler.Controls;assembly=XHandler.Controls" /// /// 您还需要添加一个从 XAML 文件所在的项目到此项目的项目引用, /// 并重新生成以避免编译错误: /// /// 在解决方案资源管理器中右击目标项目,然后依次单击 /// “添加引用”->“项目”->[浏览查找并选择此项目] /// /// /// 步骤 2) /// 继续操作并在 XAML 文件中使用控件。 /// /// /// /// [ContentProperty("DrawingCanvas")] [TemplatePart(Name = "Part_ScrollViewer", Type = typeof(ScrollViewer))] public sealed class DrawingCanvasViewer : Control { #region 依赖属性 public static readonly DependencyProperty DrawingCanvasProperty = DependencyProperty.Register("DrawingCanvas", typeof(DrawingCanvas), typeof(DrawingCanvasViewer)); /// /// 画板 /// public DrawingCanvas DrawingCanvas { get => (DrawingCanvas)this.GetValue(DrawingCanvasProperty); set => this.SetValue(DrawingCanvasProperty, value); } public static readonly DependencyProperty BackgroundImageProperty = DependencyProperty.Register("BackgroundImage", typeof(BitmapSource), typeof(DrawingCanvasViewer), new PropertyMetadata(OnBackgroundImagePropertyChanged)); private static void OnBackgroundImagePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var viewer = (DrawingCanvasViewer)d; viewer.EnsureZoom(); viewer.EnsureInnerSize(); } /// /// 背景图 /// public BitmapSource BackgroundImage { get => (BitmapSource)this.GetValue(BackgroundImageProperty); set => this.SetValue(BackgroundImageProperty, value); } public static readonly DependencyProperty InnerWidthProperty = DependencyProperty.Register("InnerWidth", typeof(Double), typeof(DrawingCanvasViewer), new PropertyMetadata(Double.NaN)); public Double InnerWidth { get => (Double)this.GetValue(InnerWidthProperty); set => this.SetValue(InnerWidthProperty, value); } public static readonly DependencyProperty InnerHeightProperty = DependencyProperty.Register("InnerHeight", typeof(Double), typeof(DrawingCanvasViewer), new PropertyMetadata(Double.NaN)); public Double InnerHeight { get => (Double)this.GetValue(InnerHeightProperty); set => this.SetValue(InnerHeightProperty, value); } public static readonly DependencyProperty ImageWidthProperty = DependencyProperty.Register("ImageWidth", typeof(Double), typeof(DrawingCanvasViewer), new PropertyMetadata(Double.NaN)); public Double ImageWidth { get => (Double)this.GetValue(ImageWidthProperty); set => this.SetValue(ImageWidthProperty, value); } public static readonly DependencyProperty ImageHeightProperty = DependencyProperty.Register("ImageHeight", typeof(Double), typeof(DrawingCanvasViewer), new PropertyMetadata(Double.NaN)); public Double ImageHeight { get => (Double)this.GetValue(ImageHeightProperty); set => this.SetValue(ImageHeightProperty, value); } public static readonly DependencyProperty ZoomProperty = DependencyProperty.Register("Zoom", typeof(Double), typeof(DrawingCanvasViewer), new PropertyMetadata(Double.NaN, OnZoomPropertyChanged)); private static void OnZoomPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var viewer = (DrawingCanvasViewer)d; viewer.EnsureInnerSize(); viewer.EnsureImageSize(); } /// /// 缩放百分比 /// public Double Zoom { get => (Double)this.GetValue(ZoomProperty); set => this.SetValue(ZoomProperty, value); } #endregion #region 构造器 static DrawingCanvasViewer() { DefaultStyleKeyProperty.OverrideMetadata(typeof(DrawingCanvasViewer), new FrameworkPropertyMetadata(typeof(DrawingCanvasViewer))); } public DrawingCanvasViewer() { this.Loaded += OnFirstLoaded; } #endregion #region 公开方法 public void ScrollBy(Double dx, Double dy) { if (!CanDrag) return; if (dx != 0) scrollViewer.ScrollToHorizontalOffset(scrollViewer.HorizontalOffset + dx); if (dy != 0) scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + dy); } #endregion #region 私有方法 public override void OnApplyTemplate() { base.OnApplyTemplate(); if (scrollViewer != null) scrollViewer.ScrollChanged -= OnScrollChanged; scrollViewer = this.Template.FindName("Part_ScrollViewer", this) as ScrollViewer; LoggerHelper.InfoLog("OnApplyTemplate"); if (scrollViewer == null) throw new NullReferenceException("模板找不到Part_ScrollViewer"); } private void OnFirstLoaded(Object sender, RoutedEventArgs e) { this.Loaded -= OnFirstLoaded; LoggerHelper.InfoLog("onLoad1"); if (BackgroundImage == null || scrollViewer == null) return; LoggerHelper.InfoLog("onLoad2"); if (Double.IsNaN(this.Zoom)) EnsureZoom(); EnsureInnerSize(); this.SizeChanged += OnSizeChanged; OnSizeChanged(sender, e); scrollViewer.ScrollChanged += OnScrollChanged; } private void EnsureZoom() { if (BackgroundImage == null || scrollViewer == null) { this.ClearValue(ZoomProperty); return; } var zoom = Math.Min(scrollViewer.ViewportWidth / BackgroundImage.Width, scrollViewer.ViewportHeight / BackgroundImage.Height); this.SetCurrentValue(ZoomProperty, Math.Min(1, zoom)); } private void EnsureInnerSize() { if (BackgroundImage == null || scrollViewer == null || Double.IsNaN(this.Zoom)) { this.ClearValue(InnerWidthProperty); this.ClearValue(InnerHeightProperty); return; } var width = BackgroundImage.Width * this.Zoom; var height = BackgroundImage.Height * this.Zoom; if (width < scrollViewer.ViewportWidth) width = scrollViewer.ViewportWidth; if (height < scrollViewer.ViewportHeight) height = scrollViewer.ViewportHeight; scrollViewer.ScrollChanged -= OnScrollChanged; this.SetValue(InnerWidthProperty, width); this.SetValue(InnerHeightProperty, height); scrollViewer.ScrollChanged += OnScrollChanged; this.EnsureScrollOffset(); } private void EnsureImageSize() { if (BackgroundImage == null || Double.IsNaN(this.Zoom)) { this.ClearValue(ImageWidthProperty); this.ClearValue(ImageHeightProperty); return; } this.SetValue(ImageWidthProperty, BackgroundImage.Width * this.Zoom); this.SetValue(ImageHeightProperty, BackgroundImage.Height * this.Zoom); } private void EnsureScrollOffset() { var offsetX = (this.InnerWidth - scrollViewer.ActualWidth) * center.X; var offsetY = (this.InnerHeight - scrollViewer.ActualHeight) * center.Y; scrollViewer.ScrollToHorizontalOffset(offsetX); scrollViewer.ScrollToVerticalOffset(offsetY); } private void OnSizeChanged(Object sender, RoutedEventArgs e) { this.EnsureScrollOffset(); } private void OnScrollChanged(Object sender, ScrollChangedEventArgs e) { if (this.InnerWidth == scrollViewer.ActualWidth) { center.X = 0.5; center.Y = 0.5; } else { center.X = (this.InnerWidth == scrollViewer.ActualWidth) ? 0 : scrollViewer.HorizontalOffset / (this.InnerWidth - scrollViewer.ActualWidth); center.Y = (this.InnerHeight == scrollViewer.ActualHeight) ? 0 : scrollViewer.VerticalOffset / (this.InnerHeight - scrollViewer.ActualHeight); } } protected override void OnPreviewMouseWheel(MouseWheelEventArgs e) { if (Keyboard.Modifiers.HasFlag(ModifierKeys.Control)) { Zoom *= 1 + (e.Delta > 0 ? scale : -scale); e.Handled = true; } } #endregion #region 属性 public Boolean CanDrag => this.scrollViewer != null && (InnerHeight > scrollViewer.ViewportHeight || InnerWidth > scrollViewer.ViewportWidth); #endregion #region 字段 private ScrollViewer scrollViewer; private Point center = new Point(0.5, 0.5); private Double scale = 0.05; #endregion } }