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
}
}