using System; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; using System.Windows.Input; using AIStudio.Wpf.DiagramDesigner.Helpers; using System.Diagnostics; namespace AIStudio.Wpf.DiagramDesigner { public class ZoomBox : Control { private Thumb zoomThumb; private Canvas zoomCanvas; private Slider zoomSlider; #region DPs #region ScrollViewer public ScrollViewer ScrollViewer { get { return (ScrollViewer)GetValue(ScrollViewerProperty); } set { SetValue(ScrollViewerProperty, value); } } public static readonly DependencyProperty ScrollViewerProperty = DependencyProperty.Register(nameof(ScrollViewer), typeof(ScrollViewer), typeof(ZoomBox)); #endregion #region FrameworkElement public static readonly DependencyProperty DesignerCanvasProperty = DependencyProperty.Register(nameof(DesignerCanvas), typeof(FrameworkElement), typeof(ZoomBox), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnDesignerCanvasChanged))); public FrameworkElement DesignerCanvas { get { return (FrameworkElement)GetValue(DesignerCanvasProperty); } set { SetValue(DesignerCanvasProperty, value); } } private static void OnDesignerCanvasChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ZoomBox target = (ZoomBox)d; FrameworkElement oldDesignerCanvas = (FrameworkElement)e.OldValue; FrameworkElement newDesignerCanvas = (FrameworkElement)e.NewValue; target.OnDesignerCanvasChanged(oldDesignerCanvas, newDesignerCanvas); } protected virtual void OnDesignerCanvasChanged(FrameworkElement oldDesignerCanvas, FrameworkElement newDesignerCanvas) { if (oldDesignerCanvas != null) { oldDesignerCanvas.LayoutUpdated -= this.DesignerCanvas_LayoutUpdated; oldDesignerCanvas.MouseDown -= this.DesignerCanvas_MouseRightButtonDown; oldDesignerCanvas.MouseUp -= this.DesignerCanvas_MouseRightButtonUp; oldDesignerCanvas.MouseMove -= this.DesignerCanvas_MouseMove; } if (newDesignerCanvas != null) { newDesignerCanvas.LayoutUpdated += this.DesignerCanvas_LayoutUpdated; newDesignerCanvas.MouseRightButtonDown += this.DesignerCanvas_MouseRightButtonDown; newDesignerCanvas.MouseRightButtonUp += this.DesignerCanvas_MouseRightButtonUp; newDesignerCanvas.MouseMove += this.DesignerCanvas_MouseMove; } } #endregion public static readonly DependencyProperty OffSetProperty = DependencyProperty.Register(nameof(OffSet), typeof(bool), typeof(ZoomBox), new UIPropertyMetadata(false)); public bool OffSet { get { return (bool)GetValue(OffSetProperty); } set { SetValue(OffSetProperty, value); } } public static readonly DependencyProperty ZoomValueProperty = DependencyProperty.Register(nameof(ZoomValue), typeof(double), typeof(ZoomBox), new UIPropertyMetadata(1d)); public double ZoomValue { get { return (double)GetValue(ZoomValueProperty); } set { SetValue(ZoomValueProperty, value); } } public static readonly DependencyProperty MaximumZoomValueProperty = DependencyProperty.Register(nameof(MaximumZoomValue), typeof(double), typeof(ZoomBox), new UIPropertyMetadata(3d)); public double MaximumZoomValue { get { return (double)GetValue(MaximumZoomValueProperty); } set { SetValue(MaximumZoomValueProperty, value); } } public static readonly DependencyProperty MinimumZoomValueProperty = DependencyProperty.Register(nameof(MinimumZoomValue), typeof(double), typeof(ZoomBox), new UIPropertyMetadata(0.5d)); public double MinimumZoomValue { get { return (double)GetValue(MinimumZoomValueProperty); } set { SetValue(MinimumZoomValueProperty, value); } } public static readonly DependencyProperty FitViewModelProperty = DependencyProperty.Register(nameof(FitViewModel), typeof(FitViewModel), typeof(ZoomBox), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnFitViewModelChanged))); public FitViewModel FitViewModel { get { return (FitViewModel)GetValue(FitViewModelProperty); } set { SetValue(FitViewModelProperty, value); } } private static void OnFitViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ZoomBox target = (ZoomBox)d; var fitviewmodel = e.NewValue as FitViewModel; if (fitviewmodel != null) { target.OnFitViewModelChanged(fitviewmodel); } } private void OnFitViewModelChanged(FitViewModel fitViewModel) { if (IsLoaded && fitViewModel != null) { if (fitViewModel.FitMode == FitMode.None) { } else if (fitViewModel.FitMode == FitMode.FitWidth) { ZoomValue = (this.ScrollViewer.ViewportWidth * fitViewModel.PaddingRate) / fitViewModel.BoundingRect.Width; } else if (fitViewModel.FitMode == FitMode.FitHeight) { ZoomValue = (this.ScrollViewer.ViewportHeight * fitViewModel.PaddingRate) / fitViewModel.BoundingRect.Height; } else if (fitViewModel.FitMode == FitMode.FitAuto) { ZoomValue = Math.Min( (this.ScrollViewer.ViewportWidth * fitViewModel.PaddingRate) / fitViewModel.BoundingRect.Width, (this.ScrollViewer.ViewportHeight * fitViewModel.PaddingRate) / fitViewModel.BoundingRect.Height ); } double xOffset, yOffset; xOffset = fitViewModel.BoundingRect.Left * ZoomValue - (this.ScrollViewer.ViewportWidth - fitViewModel.BoundingRect.Width * ZoomValue) / 2; yOffset = fitViewModel.BoundingRect.Top * ZoomValue - (this.ScrollViewer.ViewportHeight - fitViewModel.BoundingRect.Height * ZoomValue) / 2; if (OffSet) { Vector vector = System.Windows.Media.VisualTreeHelper.GetOffset(DesignerCanvas); xOffset += vector.X; yOffset += vector.Y; } this.ScrollViewer.ScrollToHorizontalOffset(xOffset); this.ScrollViewer.ScrollToVerticalOffset(yOffset); } } #endregion public ZoomBox() { this.Loaded += ZoomBox_Loaded; } private void ZoomBox_Loaded(object sender, RoutedEventArgs e) { OnFitViewModelChanged(FitViewModel); } public override void OnApplyTemplate() { base.OnApplyTemplate(); this.ScrollViewer = VisualHelper.TryFindParent(this.DesignerCanvas); if (this.ScrollViewer == null) return; this.zoomThumb = Template.FindName("PART_ZoomThumb", this) as Thumb; if (this.zoomThumb == null) throw new Exception("PART_ZoomThumb template is missing!"); this.zoomCanvas = Template.FindName("PART_ZoomCanvas", this) as Canvas; if (this.zoomCanvas == null) throw new Exception("PART_ZoomCanvas template is missing!"); this.zoomSlider = Template.FindName("PART_ZoomSlider", this) as Slider; if (this.zoomSlider == null) throw new Exception("PART_ZoomSlider template is missing!"); this.zoomThumb.DragDelta += new DragDeltaEventHandler(this.Thumb_DragDelta); this.zoomSlider.ValueChanged += new RoutedPropertyChangedEventHandler(this.ZoomSlider_ValueChanged); } private void ZoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { double scale = e.NewValue / e.OldValue; double halfViewportHeight = this.ScrollViewer.ViewportHeight / 2; double newVerticalOffset = ((this.ScrollViewer.VerticalOffset + halfViewportHeight) * scale - halfViewportHeight); double halfViewportWidth = this.ScrollViewer.ViewportWidth / 2; double newHorizontalOffset = ((this.ScrollViewer.HorizontalOffset + halfViewportWidth) * scale - halfViewportWidth); this.ScrollViewer.ScrollToHorizontalOffset(newHorizontalOffset); this.ScrollViewer.ScrollToVerticalOffset(newVerticalOffset); } private void Thumb_DragDelta(object sender, DragDeltaEventArgs e) { double scale, xOffset, yOffset; this.InvalidateScale(out scale, out xOffset, out yOffset); this.ScrollViewer.ScrollToHorizontalOffset(this.ScrollViewer.HorizontalOffset + e.HorizontalChange / scale); this.ScrollViewer.ScrollToVerticalOffset(this.ScrollViewer.VerticalOffset + e.VerticalChange / scale); } private void DesignerCanvas_LayoutUpdated(object sender, EventArgs e) { try { double scale, xOffset, yOffset; this.InvalidateScale(out scale, out xOffset, out yOffset); this.zoomThumb.Width = (this.ScrollViewer.ViewportWidth) * scale; this.zoomThumb.Height = (this.ScrollViewer.ViewportHeight) * scale; Canvas.SetLeft(this.zoomThumb, xOffset + this.ScrollViewer.HorizontalOffset * scale); Canvas.SetTop(this.zoomThumb, yOffset + this.ScrollViewer.VerticalOffset * scale); } catch { } } /// /// 用于记录鼠标按下的点 /// private Point _clickPoint = new Point(0, 0); private void DesignerCanvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e) { _clickPoint = e.GetPosition((FrameworkElement)sender); DesignerCanvas.Cursor = Cursors.Hand; } private void DesignerCanvas_MouseRightButtonUp(object sender, MouseButtonEventArgs e) { DesignerCanvas.Cursor = Cursors.Arrow; } private void DesignerCanvas_MouseMove(object sender, MouseEventArgs e) { if (e.RightButton == MouseButtonState.Pressed) { FrameworkElement cabSender = (FrameworkElement)sender; double x; double y; Point p = e.MouseDevice.GetPosition(cabSender); x = _clickPoint.X - p.X; y = _clickPoint.Y - p.Y; ScrollViewer?.ScrollToHorizontalOffset(ScrollViewer.HorizontalOffset + x); ScrollViewer?.ScrollToVerticalOffset(ScrollViewer.VerticalOffset + y); } } private void InvalidateScale(out double scale, out double xOffset, out double yOffset) { if (OffSet) { Vector vector = System.Windows.Media.VisualTreeHelper.GetOffset(DesignerCanvas); double w = DesignerCanvas.ActualWidth + vector.X * 2; double h = DesignerCanvas.ActualHeight + vector.Y * 2; // zoom canvas size double x = this.zoomCanvas.ActualWidth; double y = this.zoomCanvas.ActualHeight; double scaleX = x / w; double scaleY = y / h; scale = (scaleX < scaleY) ? scaleX : scaleY; xOffset = (x - scale * w) / 2; yOffset = (y - scale * h) / 2; } else { double w = DesignerCanvas.ActualWidth; double h = DesignerCanvas.ActualHeight; // zoom canvas size double x = this.zoomCanvas.ActualWidth; double y = this.zoomCanvas.ActualHeight; double scaleX = x / w; double scaleY = y / h; scale = (scaleX < scaleY) ? scaleX : scaleY; xOffset = (x - scale * w) / 2; yOffset = (y - scale * h) / 2; } } } public class FitViewModel { public Rect BoundingRect { get; set; } public FitMode FitMode { get; set; } public double PaddingRate { get; set; } = 0.9; } public enum FitMode { None, FitAuto, FitWidth, FitHeight, } }