using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Threading; using InkCanvasForClass.IccInkCanvas.Settings; using InkCanvasForClass.IccInkCanvas.Utils.Threading; using JetBrains.Annotations; using MatrixTransform = System.Windows.Media.MatrixTransform; namespace InkCanvasForClass.IccInkCanvas { public class IccInnerInkCanvasInfo { public Guid GUID { get; set; } public Dispatcher Dispatcher { get; set; } public DispatcherContainer Container { get; set; } } public class TimeMachineStatusUpdatedEventArgs : EventArgs { public TimeMachineStatusUpdatedEventArgs(IccBoardPage page) { Page = page; CanUndo = page.TimeMachine.CanUndo; CanRedo = page.TimeMachine.CanRedo; HistoriesCount = page.TimeMachine.CurrentHistoriesCount; } public IccBoardPage Page { get; private set; } public bool CanUndo { get; private set; } public bool CanRedo { get; private set; } public int HistoriesCount { get; private set; } } /// /// 基于InkCanvas封装的墨迹书写控件 /// public partial class IccBoard : UserControl { public BoardSettings BoardSettings { get; private set; } = new BoardSettings(); private List DispatcherInkCanvasList { get; set; } = new List(); private List BoardPages { get; set; } = new List(); private int CurrentPageIndex; #region Properties private bool _isEditingModePropertyAccessdByCodeBehind = false; public EditingMode EditingMode { get => (EditingMode)GetValue(EditingModeProperty); set { if (value == EditingMode.ShapeDrawing) throw new Exception("EditingMode.ShapeDrawing 不能被用户手动设定"); _isEditingModePropertyAccessdByCodeBehind = true; SetValue(EditingModeProperty, value); _isEditingModePropertyAccessdByCodeBehind = false; UpdateEditingMode(); } } public static readonly System.Windows.DependencyProperty EditingModeProperty = System.Windows.DependencyProperty.Register( nameof(EditingMode), typeof(EditingMode), typeof(IccBoard), new FrameworkPropertyMetadata(EditingMode.Writing, FrameworkPropertyMetadataOptions.AffectsRender, propertyChangedCallback: OnEditingModePropertyChanged)); private static void OnEditingModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var iccboard = d as IccBoard; if (iccboard != null && iccboard._isEditingModePropertyAccessdByCodeBehind) return; iccboard?.UpdateEditingMode(); } #endregion #region EditingMode private EditingMode _edittingMode; private void UpdateEditingMode() { _edittingMode = EditingMode; if (EditingMode == EditingMode.None || EditingMode == EditingMode.NoneWithHitTest) WrapperInkCanvas.EditingMode = InkCanvasEditingMode.None; IsHitTestVisible = EditingMode != EditingMode.None; if (EditingMode == EditingMode.Writing) WrapperInkCanvas.EditingMode = InkCanvasEditingMode.Ink; EraserCanvas.Visibility = EditingMode == EditingMode.GeometryErasing ? Visibility.Visible : Visibility.Collapsed; RectangleAreaEraserCanvas.Visibility = EditingMode == EditingMode.AreaErasing ? Visibility.Visible : Visibility.Collapsed; RoamingHitTestBorder.Visibility = EditingMode == EditingMode.RoamingMode ? Visibility.Visible : Visibility.Collapsed; } #endregion #region Events private static readonly System.Windows.RoutedEvent EditingModeChangedEvent = EventManager.RegisterRoutedEvent( name: "EditingModeChanged", routingStrategy: RoutingStrategy.Bubble, handlerType: typeof(System.Windows.RoutedEventHandler), ownerType: typeof(IccBoard)); private static readonly System.Windows.RoutedEvent ActiveEditingModeChangedEvent = EventManager.RegisterRoutedEvent( name: "ActiveEditingModeChanged", routingStrategy: RoutingStrategy.Bubble, handlerType: typeof(System.Windows.RoutedEventHandler), ownerType: typeof(IccBoard)); private static readonly System.Windows.RoutedEvent CurrentPageChangedEvent = EventManager.RegisterRoutedEvent( name: "CurrentPageChanged", routingStrategy: RoutingStrategy.Bubble, handlerType: typeof(System.Windows.RoutedEventHandler), ownerType: typeof(IccBoard)); private static readonly System.Windows.RoutedEvent UndoRedoStateChangedEvent = EventManager.RegisterRoutedEvent( name: "UndoRedoStateChanged", routingStrategy: RoutingStrategy.Bubble, handlerType: typeof(System.Windows.RoutedEventHandler), ownerType: typeof(IccBoard)); public event System.Windows.RoutedEventHandler EditingModeChanged { add => AddHandler(EditingModeChangedEvent, value); remove => RemoveHandler(EditingModeChangedEvent, value); } public event System.Windows.RoutedEventHandler ActiveEditingModeChanged { add => AddHandler(ActiveEditingModeChangedEvent, value); remove => RemoveHandler(ActiveEditingModeChangedEvent, value); } public event System.Windows.RoutedEventHandler CurrentPageChanged { add => AddHandler(CurrentPageChangedEvent, value); remove => RemoveHandler(CurrentPageChangedEvent, value); } /// /// 该事件接收撤销和重做状态变更的事件(注意,当前页面变更也会触发该事件。该事件接收每一页撤销和重做状态变更的事件,但是不提供具体是哪一页的时光机发生了状态变更,可以用这个事件来更新当前页面的时光机UI状态) /// public event System.Windows.RoutedEventHandler UndoRedoStateChanged { add => AddHandler(UndoRedoStateChangedEvent, value); remove => RemoveHandler(UndoRedoStateChangedEvent, value); } private bool isIgnoreRaiseCurrentPageChangedEvent = false; private void RaiseEditingModeChangedEvent() { RoutedEventArgs routedEventArgs = new RoutedEventArgs(routedEvent: EditingModeChangedEvent); RaiseEvent(routedEventArgs); } private void RaiseActiveEditingModeChangedEvent() { RoutedEventArgs routedEventArgs = new RoutedEventArgs(routedEvent: ActiveEditingModeChangedEvent); RaiseEvent(routedEventArgs); } private void RaiseCurrentPageChangedEvent() { if (isIgnoreRaiseCurrentPageChangedEvent) return; RoutedEventArgs routedEventArgs = new RoutedEventArgs(routedEvent: CurrentPageChangedEvent); RaiseEvent(routedEventArgs); RaiseUndoRedoStateChangedEvent(null); } private void RaiseUndoRedoStateChangedEvent(TimeMachine t) { RoutedEventArgs routedEventArgs = new RoutedEventArgs(routedEvent: UndoRedoStateChangedEvent); RaiseEvent(routedEventArgs); if (t != null) TimeMachineStatusUpdated?.Invoke(this, new TimeMachineStatusUpdatedEventArgs(BoardPages.Find(page=>page.TimeMachine.Equals(t)))); } /// /// 该事件用于接收时光机状态变更的事件(接收所有页面的时光机状态变化) /// public event EventHandler TimeMachineStatusUpdated; #endregion #region BoardSettings private void RegisterEventsForBoardSettings() { BoardSettings.NibWidthChanged += (s,e)=>WrapperInkCanvas.DefaultDrawingAttributes.Width = BoardSettings.NibWidth; BoardSettings.NibHeightChanged += (s,e)=>WrapperInkCanvas.DefaultDrawingAttributes.Height = BoardSettings.NibHeight; BoardSettings.NibColorChanged += (s,e)=>WrapperInkCanvas.DefaultDrawingAttributes.Color = BoardSettings.NibColor; } #endregion #region InkCanvas Manager /// /// 记录所有已注册了Strokes_OnStrokesChanged事件的InkCanvas的GUID /// private Dictionary _innerInkCanvas_Strokes_OnStrokesChanged_wrappers = new Dictionary(); /// /// 记录所有已登记要触发Stroke_OnDrawingAttributesChanged事件的InkCanvas的GUID /// (为什么不是已注册?因为这个事件是针对一条Stroke而言的,这里只是记录这个InkCanvas将会调用这个事件) /// private Dictionary _innerInkCanvas_Stroke_OnDrawingAttributesChanged_wrappers = new Dictionary(); /// /// 记录所有已登记要触发Stroke_OnStylusPointsChanged事件的InkCanvas的GUID /// (为什么不是已注册?因为这个事件是针对一条Stroke而言的,这里只是记录这个InkCanvas将会调用这个事件) /// private Dictionary _innerInkCanvas_Stroke_OnStylusPointsChanged_wrappers = new Dictionary(); /// /// 记录所有已登记要触发Stroke_OnStylusPointsChanged事件的InkCanvas的GUID /// (为什么不是已注册?因为这个事件是针对一条Stroke而言的,这里只是记录这个InkCanvas将会调用这个事件) /// private Dictionary _innerInkCanvas_Stroke_OnStylusPointsReplaced_wrappers = new Dictionary(); private async Task AddIccInkCanvas() { var screenW = SystemParameters.PrimaryScreenWidth; var screenH = SystemParameters.PrimaryScreenHeight; var fullWidth = screenW * 257; var fullHeight = screenH * 417; var left = 0 - screenW * 128; var top = 0 - screenH * 208; var di = new IccDispatcherInkCanvasInfo(); await di.InitDispatcher(); var control = await di.Dispatcher.InvokeAsync(() => new InkCanvas() { Background = new SolidColorBrush(Colors.White), Width = fullWidth, Height = fullHeight, }); var dc = new DispatcherContainer() { Width = fullWidth, Height = fullHeight, }; await dc.SetChildAsync(control); InkCanvasHostCanvas.Children.Add(dc); Canvas.SetTop(dc, top); Canvas.SetLeft(dc, left); var info = new IccInnerInkCanvasInfo() { GUID = di.GUID, Dispatcher = di.Dispatcher, Container = dc }; DispatcherInkCanvasList.Add(info); // 注册事件 RegisterTimeMachineEventsToInkCanvas(info.Dispatcher, info.Container, info.GUID); return info; } private void RemoveIccInkCanvas(IccInnerInkCanvasInfo item) { item.Dispatcher.BeginInvokeShutdown(DispatcherPriority.Render); InkCanvasHostCanvas.Children.Remove(item.Container); DispatcherInkCanvasList.Remove(item); } private void UpdateInnerInkCanvasVisibility(IccBoardPage item) { WrapperInkCanvas.Strokes.Clear(); foreach (UIElement child in InkCanvasHostCanvas.Children) { child.Visibility = Visibility.Collapsed; if (child.Equals(item.Container)) child.Visibility = Visibility.Visible; } } /// /// 添加时光机支持到DispatcherInkCanvas中(这里是直接以GUID为标识符的) /// /// /// /// private void RegisterTimeMachineEventsToInkCanvas(Dispatcher dispatcher, DispatcherContainer container, Guid GUID) { Task.Run(() => { dispatcher.Invoke(() => { var ic = container.Child as InkCanvas; // Strokes_OnStrokesChanged if (!_innerInkCanvas_Strokes_OnStrokesChanged_wrappers.ContainsKey(GUID)) { _innerInkCanvas_Strokes_OnStrokesChanged_wrappers.Add(GUID, (o, args) => { InnerInkCanvas_Strokes_OnStrokesChanged(o, dispatcher, container, GUID, args); }); ic.Strokes.StrokesChanged += _innerInkCanvas_Strokes_OnStrokesChanged_wrappers[GUID]; } // Stroke_OnDrawingAttributesChanged if (!_innerInkCanvas_Stroke_OnDrawingAttributesChanged_wrappers.ContainsKey(GUID)) { _innerInkCanvas_Stroke_OnDrawingAttributesChanged_wrappers.Add(GUID, (o, args) => { InnerInkCanvas_Stroke_OnDrawingAttributesChanged(o, dispatcher, container, GUID, args); }); } // Stroke_OnStylusPointsChanged if (!_innerInkCanvas_Stroke_OnStylusPointsChanged_wrappers.ContainsKey(GUID)) { _innerInkCanvas_Stroke_OnStylusPointsChanged_wrappers.Add(GUID, (o, args) => { InnerInkCanvas_Stroke_OnStylusPointsChanged(o, dispatcher, container, GUID, args); }); } // Stroke_OnStylusPointsReplaced if (!_innerInkCanvas_Stroke_OnStylusPointsReplaced_wrappers.ContainsKey(GUID)) { _innerInkCanvas_Stroke_OnStylusPointsReplaced_wrappers.Add(GUID, (o, args) => { InnerInkCanvas_Stroke_OnStylusPointsReplaced(o, dispatcher, container, GUID, args); }); } }); }); } /// /// 取消注册时光机支持 /// /// /// /// private void UnRegisterTimeMachineEventsToInkCanvas(Dispatcher dispatcher, DispatcherContainer container, Guid GUID) { Task.Run(() => { dispatcher.Invoke(() => { var ic = container.Child as InkCanvas; // Strokes_OnStrokesChanged if (_innerInkCanvas_Strokes_OnStrokesChanged_wrappers.ContainsKey(GUID)) { _innerInkCanvas_Strokes_OnStrokesChanged_wrappers.Remove(GUID); ic.Strokes.StrokesChanged -= _innerInkCanvas_Strokes_OnStrokesChanged_wrappers[GUID]; } // Stroke_OnDrawingAttributesChanged if (_innerInkCanvas_Stroke_OnDrawingAttributesChanged_wrappers.ContainsKey(GUID)) { _innerInkCanvas_Stroke_OnDrawingAttributesChanged_wrappers.Remove(GUID); } // Stroke_OnStylusPointsChanged if (_innerInkCanvas_Stroke_OnStylusPointsChanged_wrappers.ContainsKey(GUID)) { _innerInkCanvas_Stroke_OnStylusPointsChanged_wrappers.Remove(GUID); } // Stroke_OnStylusPointsReplaced if (_innerInkCanvas_Stroke_OnStylusPointsReplaced_wrappers.ContainsKey(GUID)) { _innerInkCanvas_Stroke_OnStylusPointsReplaced_wrappers.Remove(GUID); } }); }); } #endregion #region Multi-Pages /// /// 根据索引值获取页面 /// /// /// public IccBoardPage this[int index] => BoardPages[index]; /// /// 根据InkCanvas的GUID获取页面 /// /// /// public IccBoardPage this[Guid GUID] => BoardPages.Find(page => page.GUID == GUID); /// /// 获取总页数 /// public int PagesCount { get => BoardPages.Count; } /// /// 获取当前页面的索引值(如果需要展示页数的话,获取到的还需要加1) /// public int CurrentPage { get => CurrentPageIndex; } /// /// 获取到当前页面的 IccBoardPage 对象 /// public IccBoardPage CurrentPageItem { get => BoardPages[CurrentPageIndex]; } /// /// 添加新的白板页面(同时会创建新的DispatcherInkCanvas,如果想用现有的DispatcherInkCanvas插入为新页面的话,请使用 ) /// /// 选择页面插入的模式 /// 指定是否在插入页面后跳转到该页面 /// public async Task AddPage(BoardPageAppendMode? boardPageAppendMode = BoardPageAppendMode.AppendAfterItem, bool? jumpToPageWhenAdded = true) { var info = await AddIccInkCanvas(); var page = new IccBoardPage() { Container = info.Container, GUID = info.GUID, Dispatcher = info.Dispatcher, InkCanvas = await info.Dispatcher.InvokeAsync(() => info.Container.Child) as InkCanvas }; page.TimeMachine.UndoRedoStateChanged += RaiseUndoRedoStateChangedEvent; var appendMode = boardPageAppendMode ?? BoardPageAppendMode.AppendAfterItem; var insertP = 0; if (appendMode == BoardPageAppendMode.AppendAfterItem) { insertP = Math.Max(Math.Min(CurrentPageIndex + 1, BoardPages.Count),0); } else if (appendMode == BoardPageAppendMode.AppendBeforeItem) { insertP = Math.Max(Math.Min(CurrentPageIndex - 1, BoardPages.Count),0); } else if (appendMode == BoardPageAppendMode.AppendToListStart) { insertP = 0; } else if (appendMode == BoardPageAppendMode.AppendToListEnd) { insertP = BoardPages.Count; } BoardPages.Insert(insertP, page); isIgnoreRaiseCurrentPageChangedEvent = false; if (jumpToPageWhenAdded??true) { UpdateInnerInkCanvasVisibility(page); CurrentPageIndex = BoardPages.IndexOf(page); RaiseCurrentPageChangedEvent(); } return page; } /// /// 从现有的DispatcherInkCanvas中添加页面(和AddPage的区别就是不会调用AddIccInkCanvas,不会注册事件,也不会对DispatcherInkCanvas做任何初始化操作) /// (注意:如果使用该方法,需要自行处理TimeMachine相关的东西!) /// /// /// /// /// /// /// public IccBoardPage AddPageFromExistedDispatcherInkCanvas(DispatcherContainer container, Dispatcher dispatcher, Guid? GUID, BoardPageAppendMode? boardPageAppendMode = BoardPageAppendMode.AppendAfterItem, bool? jumpToPageWhenAdded = true) { var page = new IccBoardPage() { Container = container, GUID = GUID??Guid.NewGuid(), Dispatcher = dispatcher, InkCanvas = dispatcher.Invoke(() => container.Child) as InkCanvas }; var appendMode = boardPageAppendMode ?? BoardPageAppendMode.AppendAfterItem; var insertP = 0; if (appendMode == BoardPageAppendMode.AppendAfterItem) { insertP = Math.Max(Math.Min(CurrentPageIndex + 1, BoardPages.Count),0); } else if (appendMode == BoardPageAppendMode.AppendBeforeItem) { insertP = Math.Max(Math.Min(CurrentPageIndex - 1, BoardPages.Count),0); } else if (appendMode == BoardPageAppendMode.AppendToListStart) { insertP = 0; } else if (appendMode == BoardPageAppendMode.AppendToListEnd) { insertP = BoardPages.Count; } BoardPages.Insert(insertP, page); isIgnoreRaiseCurrentPageChangedEvent = false; if (jumpToPageWhenAdded??true) { UpdateInnerInkCanvasVisibility(page); CurrentPageIndex = BoardPages.IndexOf(page); RaiseCurrentPageChangedEvent(); } return page; } /// /// 切换到上一页 /// /// 跳转的页数,默认为1 public void GoToPreviousPage(int? step = 1) { var ci = CurrentPageIndex - step??1; if (ci < 0) ci = 0; var page = BoardPages[ci]; UpdateInnerInkCanvasVisibility(page); if (!ci.Equals(CurrentPageIndex)) { CurrentPageIndex = ci; RaiseCurrentPageChangedEvent(); } } /// /// 切换到下一页 /// /// 跳转的页数,默认为1 public void GoToNextPage(int? step = 1) { var ci = CurrentPageIndex + step??1; if (ci > BoardPages.Count - 1) ci = BoardPages.Count - 1; var page = BoardPages[ci]; UpdateInnerInkCanvasVisibility(page); if (!ci.Equals(CurrentPageIndex)) { CurrentPageIndex = ci; RaiseCurrentPageChangedEvent(); } } /// /// 跳转到指定索引的页面 /// /// 索引 public void GotoPage(int index) { UpdateInnerInkCanvasVisibility(BoardPages[index]); if (!index.Equals(CurrentPageIndex)) { CurrentPageIndex = index; RaiseCurrentPageChangedEvent(); } } /// /// 跳转到指定页面 /// /// 页面 public void GotoPage(IccBoardPage page) { var index = BoardPages.IndexOf(page); UpdateInnerInkCanvasVisibility(page); if (!index.Equals(CurrentPageIndex)) { CurrentPageIndex = index; RaiseCurrentPageChangedEvent(); } } /// /// 指示当前页面是否是最后一页,如果只有一页会返回true /// public bool IsCurrentLastPage => CurrentPageIndex == BoardPages.Count - 1; /// /// 指示当前页面是否是第一页,如果只有一页会返回true /// public bool IsCurrentFirstPage => CurrentPageIndex == 0; private async Task _RemovePage(IccBoardPage page, bool? isDisposeInstance = true, PageSwitchMode? pageSwitchMode = PageSwitchMode.SwitchToPreviousPage) { var isCurrent = CurrentPageItem == page; isIgnoreRaiseCurrentPageChangedEvent = true; if (isDisposeInstance??true) RemoveIccInkCanvas(DispatcherInkCanvasList.Find(info => info.GUID.Equals(page.GUID))); if (isCurrent) { var switchMode = pageSwitchMode ?? PageSwitchMode.SwitchToPreviousPage; if (IsCurrentFirstPage) GoToNextPage(); else if (IsCurrentLastPage) GoToPreviousPage(); else if (!IsCurrentFirstPage && !IsCurrentLastPage) { if (switchMode == PageSwitchMode.SwitchToPreviousPage) GoToPreviousPage(); else if (switchMode == PageSwitchMode.SwitchToNextPage) GoToNextPage(); } } page.TimeMachine.UndoRedoStateChanged -= RaiseUndoRedoStateChangedEvent; BoardPages.Remove(page); isIgnoreRaiseCurrentPageChangedEvent = false; if (BoardPages.Count == 0) await AddPage(BoardPageAppendMode.AppendToListEnd); else RaiseCurrentPageChangedEvent(); } /// /// 移除指定索引的页面,如果只有1页,执行该方法会删除该页面并新建一个页面。 /// /// 索引 /// 指定是否销毁该页面的Dispatcher,销毁后无法再使用该实例添加到Board! /// 删除当前激活页面后切换到临近页面的行为,如果删除的页面不是当前页面则不会生效 public async Task RemovePageAt(int index, bool? isDisposeInstance = true, PageSwitchMode? pageSwitchMode = PageSwitchMode.SwitchToPreviousPage) { var page = BoardPages[index]; await _RemovePage(page, isDisposeInstance, pageSwitchMode); } /// /// 移除当前页面,如果只有1页,执行该方法会删除当前页面并新建一个页面。 /// /// 指定是否销毁该页面的Dispatcher,销毁后无法再使用该实例添加到Board! /// 删除当前激活页面后切换到临近页面的行为,如果删除的页面不是当前页面则不会生效 public async Task RemovePage(bool? isDisposeInstance = true, PageSwitchMode? pageSwitchMode = PageSwitchMode.SwitchToPreviousPage) { var page = CurrentPageItem; await _RemovePage(page, isDisposeInstance, pageSwitchMode); } /// /// 移除指定的页面,如果只有1页,执行该方法会删除该页面并新建一个页面。 /// /// 页面 /// 指定是否销毁该页面的Dispatcher,销毁后无法再使用该实例添加到Board! /// 删除当前激活页面后切换到临近页面的行为,如果删除的页面不是当前页面则不会生效 public async Task RemovePage(IccBoardPage page, bool? isDisposeInstance = true, PageSwitchMode? pageSwitchMode = PageSwitchMode.SwitchToPreviousPage) { await _RemovePage(page, isDisposeInstance, pageSwitchMode); } #endregion #region Dynamic Renderer public static readonly Guid StrokeUniqueIdKeyGuid = Guid.Parse("71600a68-0a93-4e71-8120-5a7fad5de7e2"); private async void WrapperInkCanvas_Loaded(object sender, RoutedEventArgs e) { var ic = (IccInkCanvas)sender; // 启动时自动修改 InkCanvas 的大小 var screenW = SystemParameters.PrimaryScreenWidth; var screenH = SystemParameters.PrimaryScreenHeight; var fullWidth = screenW * 257; var fullHeight = screenH * 417; var left = 0 - screenW * 128; var top = 0 - screenH * 208; ic.Width = fullWidth; ic.Height = fullHeight; Canvas.SetLeft(ic, left); Canvas.SetTop(ic, top); ic.DefaultDrawingAttributes.Width = BoardSettings.NibWidth; ic.DefaultDrawingAttributes.Height = BoardSettings.NibHeight; ic.DefaultDrawingAttributes.Color = BoardSettings.NibColor; ic.StrokeCollected += IccWrapperInkCanvas_StrokeCollected; ic.BoardSettings = BoardSettings; // BoardSettings 事件注册 RegisterEventsForBoardSettings(); if (BoardPages.Count == 0) { await AddPage(); } } private void IccWrapperInkCanvas_StrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e) { var ic = (IccInkCanvas)sender; // add unique id to stroke e.Stroke.AddPropertyData(StrokeUniqueIdKeyGuid, BoardPages[CurrentPageIndex].LastStrokeID); BoardPages[CurrentPageIndex].LastStrokeID += 1L; Task.Run(() => { BoardPages[CurrentPageIndex].Dispatcher.Invoke(() => { var _c = (InkCanvas)BoardPages[CurrentPageIndex].Container.Child; _c.Strokes.Add(e.Stroke); }); Task.Delay(100); Dispatcher.InvokeAsync(()=>ic.Strokes.Remove(e.Stroke)); }); } #endregion #region Eraser Overlay private IncrementalStrokeHitTester eraserStrokeHitTester; private bool isEraserOverlayPointerDown = false; private void EraserOverlayCanvas_Loaded(object sender, RoutedEventArgs e) { var bd = (Canvas)sender; bd.StylusDown += ((o, args) => { e.Handled = true; if (args.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus) ((Canvas)o).CaptureStylus(); EraserOverlay_PointerDown(sender); }); bd.StylusUp += ((o, args) => { e.Handled = true; if (args.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus) ((Canvas)o).ReleaseStylusCapture(); EraserOverlay_PointerUp(sender); }); bd.StylusMove += ((o, args) => { e.Handled = true; EraserOverlay_PointerMove(sender, args.GetPosition(WrapperInkCanvas), args.GetPosition(this)); }); bd.MouseDown += ((o, args) => { ((Canvas)o).CaptureMouse(); EraserOverlay_PointerDown(sender); }); bd.MouseUp += ((o, args) => { ((Canvas)o).ReleaseMouseCapture(); EraserOverlay_PointerUp(sender); }); bd.MouseMove += ((o, args) => { EraserOverlay_PointerMove(sender, args.GetPosition(WrapperInkCanvas), args.GetPosition(this)); }); BoardSettings.EraserTypeChanged += (o, args) => { if (BoardSettings.EraserType == EraserType.Rectangle) EraserFeedback.Source = FindResource("RectangleEraserImageSource") as DrawingImage; else if (BoardSettings.EraserType == EraserType.Ellipse) EraserFeedback.Source = FindResource("EllipseEraserImageSource") as DrawingImage; }; EraserFeedback.Source = FindResource("RectangleEraserImageSource") as DrawingImage; } private void EraserOverlay_PointerDown(object sender) { if (isEraserOverlayPointerDown) return; if (CurrentPageItem.Dispatcher.Invoke(() => ((InkCanvas)CurrentPageItem.Container.Child).Strokes.Count) == 0) return; isEraserOverlayPointerDown = true; EraserFeedback.Width = Math.Max(BoardSettings.EraserSize,10); EraserFeedback.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity)); EraserFeedback.Visibility = Visibility.Collapsed; StylusShape stylusTipShape; if (BoardSettings.EraserType == EraserType.Ellipse) stylusTipShape = new EllipseStylusShape(Math.Max(BoardSettings.EraserSize-4, 10), Math.Max(BoardSettings.EraserSize-4, 10)); else stylusTipShape = new RectangleStylusShape(BoardSettings.EraserSize - 4, (BoardSettings.EraserSize-4) * 56 / 38); // init hittester Task.Run(() => { eraserStrokeHitTester = CurrentPageItem.Dispatcher.Invoke(() => ((InkCanvas)CurrentPageItem.Container.Child).Strokes.GetIncrementalStrokeHitTester(stylusTipShape)); CurrentPageItem.Dispatcher.Invoke(() => { eraserStrokeHitTester.StrokeHit += (obj, e) => { var stks = ((InkCanvas)CurrentPageItem.Container.Child).Strokes; StrokeCollection eraseResult = e.GetPointEraseResults(); StrokeCollection strokesToReplace = new StrokeCollection { e.HitStroke }; if (eraseResult.Any()) { stks.Replace(strokesToReplace, eraseResult); } else { stks.Remove(strokesToReplace); } }; }); }); } private void EraserOverlay_PointerUp(object sender) { if (!isEraserOverlayPointerDown) return; isEraserOverlayPointerDown = false; EraserFeedback.Visibility = Visibility.Collapsed; if (ReplacedStroke != null || AddedStroke != null) { CurrentPageItem.TimeMachine.CommitStrokeEraseHistory(ReplacedStroke, AddedStroke); AddedStroke = null; ReplacedStroke = null; } Task.Run(() => { CurrentPageItem.Dispatcher.Invoke(() => { eraserStrokeHitTester.EndHitTesting(); }); eraserStrokeHitTester = null; }); } private void EraserOverlay_PointerMove(object sender, Point ptInInkCanvas, Point ptInEraserOverlay) { if (!isEraserOverlayPointerDown) return; if (EraserFeedback.Visibility == Visibility.Collapsed) EraserFeedback.Visibility = Visibility.Visible; EraserFeedbackTranslateTransform.X = ptInEraserOverlay.X - EraserFeedback.ActualWidth /2; EraserFeedbackTranslateTransform.Y = ptInEraserOverlay.Y - EraserFeedback.ActualHeight /2; // erase stroke try { CurrentPageItem.Dispatcher.Invoke(() => { eraserStrokeHitTester.AddPoint(ptInInkCanvas); }); } catch{} } #endregion #region Rectangle Area Eraser private bool isRectangleAreaEraserCanvasPointerDown = false; private Point? rectangleAreaEraserCanvas_firstPt; private Point? rectangleAreaEraserCanvas_firstPtInIC; private Point? rectangleAreaEraserCanvas_lastPt; private Point? rectangleAreaEraserCanvas_lastPtInIC; private void HostCanvas_Loaded(object sender, RoutedEventArgs e) { var ca = (Canvas)sender; HostCanvasClipGeometry1.Rect = new Rect(new Size(ca.Width, ca.Height)); } private void HostCanvas_SizeChanged(object sender, SizeChangedEventArgs e) { HostCanvasClipGeometry1.Rect = new Rect(e.NewSize); } private void RectangleAreaEraserCanvas_Loaded(object sender, RoutedEventArgs e) { var ca = (Canvas)sender; ca.StylusDown += ((o, args) => { e.Handled = true; if (args.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus) ((Canvas)o).CaptureStylus(); RectangleAreaEraserCanvas_PointerDown(sender); }); ca.StylusUp += ((o, args) => { e.Handled = true; if (args.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus) ((Canvas)o).ReleaseStylusCapture(); RectangleAreaEraserCanvas_PointerUp(sender); }); ca.StylusMove += ((o, args) => { e.Handled = true; RectangleAreaEraserCanvas_PointerMove(sender, args.GetPosition(WrapperInkCanvas), args.GetPosition(this)); }); ca.MouseDown += ((o, args) => { ((Canvas)o).CaptureMouse(); RectangleAreaEraserCanvas_PointerDown(sender); }); ca.MouseUp += ((o, args) => { ((Canvas)o).ReleaseMouseCapture(); RectangleAreaEraserCanvas_PointerUp(sender); }); ca.MouseMove += ((o, args) => { RectangleAreaEraserCanvas_PointerMove(sender, args.GetPosition(WrapperInkCanvas), args.GetPosition(this)); }); AreaErasingFeedback.Visibility = Visibility.Collapsed; } private void RectangleAreaEraserCanvas_PointerDown(object sender) { if (isRectangleAreaEraserCanvasPointerDown) return; isRectangleAreaEraserCanvasPointerDown = true; HostCanvasClipGeometry.Geometry2 = new RectangleGeometry(); rectangleAreaEraserCanvas_firstPt = null; rectangleAreaEraserCanvas_lastPt = null; rectangleAreaEraserCanvas_firstPtInIC = null; rectangleAreaEraserCanvas_lastPtInIC = null; } private void RectangleAreaEraserCanvas_PointerUp(object sender) { if (!isRectangleAreaEraserCanvasPointerDown) return; isRectangleAreaEraserCanvasPointerDown = false; HostCanvasClipGeometry.Geometry2 = Geometry.Empty; var rect = new Rect(rectangleAreaEraserCanvas_firstPtInIC ?? new Point(0, 0), rectangleAreaEraserCanvas_lastPtInIC ?? new Point(0, 0)); var stylusShape = new RectangleStylusShape(rect.Width, rect.Height); Task.Run(() => { CurrentPageItem.Dispatcher.Invoke(() => { ((InkCanvas)CurrentPageItem.Container.Child).Strokes.Erase( new Point[] { new Point(rect.Left + rect.Width / 2, rect.Top + rect.Height / 2) }, stylusShape); }); if (ReplacedStroke != null || AddedStroke != null) { CurrentPageItem.TimeMachine.CommitStrokeEraseHistory(ReplacedStroke, AddedStroke); AddedStroke = null; ReplacedStroke = null; } }); rectangleAreaEraserCanvas_firstPt = null; rectangleAreaEraserCanvas_lastPt = null; rectangleAreaEraserCanvas_firstPtInIC = null; rectangleAreaEraserCanvas_lastPtInIC = null; AreaErasingFeedback.Visibility = Visibility.Collapsed; } private void RectangleAreaEraserCanvas_PointerMove(object sender, Point ptInInkCanvas, Point ptInEraserOverlay) { if (!isRectangleAreaEraserCanvasPointerDown) return; if (rectangleAreaEraserCanvas_firstPt == null) { rectangleAreaEraserCanvas_firstPt = ptInEraserOverlay; rectangleAreaEraserCanvas_firstPtInIC = ptInInkCanvas; } rectangleAreaEraserCanvas_lastPt = ptInEraserOverlay; rectangleAreaEraserCanvas_lastPtInIC = ptInInkCanvas; // update geometry clip ((RectangleGeometry)HostCanvasClipGeometry.Geometry2).Rect = new Rect( rectangleAreaEraserCanvas_firstPt ?? new Point(0, 0), rectangleAreaEraserCanvas_lastPt ?? new Point(0, 0)); // update fedback if (AreaErasingFeedback.Visibility == Visibility.Collapsed) AreaErasingFeedback.Visibility = Visibility.Visible; Canvas.SetTop(AreaErasingFeedback,Math.Min(((Point)rectangleAreaEraserCanvas_firstPt).Y,((Point)rectangleAreaEraserCanvas_lastPt).Y)); Canvas.SetLeft(AreaErasingFeedback,Math.Min(((Point)rectangleAreaEraserCanvas_firstPt).X,((Point)rectangleAreaEraserCanvas_lastPt).X)); AreaErasingFeedback.Width = Math.Abs(((Point)rectangleAreaEraserCanvas_firstPt).X - ((Point)rectangleAreaEraserCanvas_lastPt).X); AreaErasingFeedback.Height = Math.Abs(((Point)rectangleAreaEraserCanvas_firstPt).Y - ((Point)rectangleAreaEraserCanvas_lastPt).Y); } #endregion #region TimeMachine private bool _isTimeMachineThreadTaskDone = true; // 指示时光机该怎么判定本次历史记录提交 private CommitReason _currentCommitType = CommitReason.UserInput; private StrokeCollection ReplacedStroke; private StrokeCollection AddedStroke; private Dictionary> StylusPointsChangedHistory; private Dictionary StrokeInitialHistory = new Dictionary(); private Dictionary> DrawingAttributesHistory = new Dictionary>(); private Dictionary> DrawingAttributesHistoryFlag = new Dictionary>() { { DrawingAttributeIds.Color, new List() }, { DrawingAttributeIds.DrawingFlags, new List() }, { DrawingAttributeIds.IsHighlighter, new List() }, { DrawingAttributeIds.StylusHeight, new List() }, { DrawingAttributeIds.StylusTip, new List() }, { DrawingAttributeIds.StylusTipTransform, new List() }, { DrawingAttributeIds.StylusWidth, new List() } }; /// /// 根据传入的History项目,将历史记录应用到指定页面上去 /// /// /// private void ApplyHistoryToPage(IccBoardPage page, TimeMachineHistory history) { _currentCommitType = CommitReason.CodeInput; page.Dispatcher.InvokeAsync(() => { var inkcanvas = page.Container.Child as InkCanvas; while (!_isTimeMachineThreadTaskDone) { Thread.Sleep(1); } _isTimeMachineThreadTaskDone = false; if (history.CommitType == TimeMachineHistoryType.UserInput) { foreach (var strokes in history.CurrentStroke) { Trace.WriteLine(strokes); if (!inkcanvas.Strokes.Contains(strokes) && !history.StrokeHasBeenCleared) inkcanvas.Strokes.Add(strokes); else if (inkcanvas.Strokes.Contains(strokes) && history.StrokeHasBeenCleared) { foreach (var _stroke in inkcanvas.Strokes.Where(s => s.ContainsPropertyData(StrokeUniqueIdKeyGuid) && s.GetPropertyData(StrokeUniqueIdKeyGuid) == strokes.GetPropertyData(StrokeUniqueIdKeyGuid)).ToArray()) inkcanvas.Strokes.Remove(_stroke); } } } else if (history.CommitType == TimeMachineHistoryType.ShapeRecognition) { foreach (var strokes in history.CurrentStroke) { if (inkcanvas.Strokes.Contains(strokes) && history.StrokeHasBeenCleared) foreach (var _stroke in inkcanvas.Strokes.Where(s => s.ContainsPropertyData(StrokeUniqueIdKeyGuid) && s.GetPropertyData(StrokeUniqueIdKeyGuid) == strokes.GetPropertyData(StrokeUniqueIdKeyGuid)).ToArray()) inkcanvas.Strokes.Remove(_stroke); else if (!inkcanvas.Strokes.Contains(strokes) && !history.StrokeHasBeenCleared) inkcanvas.Strokes.Add(strokes); } foreach (var strokes in history.ReplacedStroke) { if (!inkcanvas.Strokes.Contains(strokes) && !history.StrokeHasBeenCleared) foreach (var _stroke in inkcanvas.Strokes.Where(s => s.ContainsPropertyData(StrokeUniqueIdKeyGuid) && s.GetPropertyData(StrokeUniqueIdKeyGuid) == strokes.GetPropertyData(StrokeUniqueIdKeyGuid)).ToArray()) inkcanvas.Strokes.Remove(_stroke); else if (inkcanvas.Strokes.Contains(strokes) && history.StrokeHasBeenCleared) inkcanvas.Strokes.Add(strokes); } } else if (history.CommitType == TimeMachineHistoryType.StylusPoints) { foreach (var currentStroke in history.StylusPointsDictionary) { if (inkcanvas.Strokes.Contains(currentStroke.Key)) currentStroke.Key.StylusPoints = history.StrokeHasBeenCleared ? currentStroke.Value.Item1 : currentStroke.Value.Item2; } } else if (history.CommitType == TimeMachineHistoryType.DrawingAttributes) { foreach (var currentStroke in history.DrawingAttributes) { if (inkcanvas.Strokes.Contains(currentStroke.Key)) currentStroke.Key.DrawingAttributes = history.StrokeHasBeenCleared ? currentStroke.Value.Item1 : currentStroke.Value.Item2; } } else if (history.CommitType == TimeMachineHistoryType.Erased) { if (!history.StrokeHasBeenCleared) { if (history.CurrentStroke != null) foreach (var currentStroke in history.CurrentStroke) if (!inkcanvas.Strokes.Contains(currentStroke)) inkcanvas.Strokes.Add(currentStroke); if (history.ReplacedStroke != null) foreach (var replacedStroke in history.ReplacedStroke) if (inkcanvas.Strokes.Contains(replacedStroke)) inkcanvas.Strokes.Remove(replacedStroke); } else { if (history.ReplacedStroke != null) foreach (var replacedStroke in history.ReplacedStroke) if (!inkcanvas.Strokes.Contains(replacedStroke)) inkcanvas.Strokes.Add(replacedStroke); if (history.CurrentStroke != null) foreach (var currentStroke in history.CurrentStroke) if (inkcanvas.Strokes.Contains(currentStroke)) inkcanvas.Strokes.Remove(currentStroke); } } _currentCommitType = CommitReason.UserInput; _isTimeMachineThreadTaskDone = true; }); } /// /// 批量应用多个历史记录到页面上 /// /// /// private void ApplyHistoriesToPage(IccBoardPage page, TimeMachineHistory[] histories) { foreach (var timeMachineHistory in histories) { ApplyHistoryToPage(page,timeMachineHistory); } } /// /// 内部的InkCanvas触发了Strokes.StrokesChanged事件 /// /// 发送事件的InkCanvas(需要用传入的Dispatcher操作) /// 这个InkCanvas所在的Dispatcher对象 /// 这个InkCanvas所存放的DispatcherContainer /// InkCanvas(也是页面的)GUID /// StrokeCollectionChangedEventArgs事件 private void InnerInkCanvas_Strokes_OnStrokesChanged(object sender, Dispatcher dispatcher, DispatcherContainer container, Guid GUID, StrokeCollectionChangedEventArgs eventArgs) { Trace.WriteLine($"GUID:{GUID} 触发了Strokes_OnStrokesChanged,第{BoardPages.IndexOf(BoardPages.Find(page=>page.GUID.Equals(GUID)))+1}页"); foreach (var stroke in eventArgs?.Removed) { stroke.DrawingAttributesChanged -= _innerInkCanvas_Stroke_OnDrawingAttributesChanged_wrappers[GUID]; stroke.StylusPointsReplaced -= _innerInkCanvas_Stroke_OnStylusPointsReplaced_wrappers[GUID]; stroke.StylusPointsChanged -= _innerInkCanvas_Stroke_OnStylusPointsChanged_wrappers[GUID]; } foreach (var stroke in eventArgs?.Added) { stroke.DrawingAttributesChanged += _innerInkCanvas_Stroke_OnDrawingAttributesChanged_wrappers[GUID]; stroke.StylusPointsReplaced += _innerInkCanvas_Stroke_OnStylusPointsReplaced_wrappers[GUID]; stroke.StylusPointsChanged += _innerInkCanvas_Stroke_OnStylusPointsChanged_wrappers[GUID]; } if (_currentCommitType == CommitReason.CodeInput || _currentCommitType == CommitReason.ShapeDrawing) return; // 橡皮擦擦除墨迹和时光机对接 if ((eventArgs?.Added.Count != 0 || eventArgs?.Removed.Count != 0) && (_edittingMode == EditingMode.GeometryErasing || _edittingMode == EditingMode.AreaErasing)) { if (AddedStroke == null) AddedStroke = new StrokeCollection(); if (ReplacedStroke == null) ReplacedStroke = new StrokeCollection(); try { AddedStroke.Add(eventArgs.Added); ReplacedStroke.Add(eventArgs.Removed); } catch {} Trace.WriteLine(ReplacedStroke.Count); return; } // 有新墨迹 if (eventArgs.Added.Count != 0) { // 判断是否是形状识别新增的墨迹 if (_currentCommitType == CommitReason.ShapeRecognition) { BoardPages.Find(page=>page.GUID==GUID).TimeMachine.CommitStrokeShapeHistory(ReplacedStroke, eventArgs.Added); ReplacedStroke = null; return; } else { BoardPages.Find(page=>page.GUID==GUID).TimeMachine.CommitStrokeUserInputHistory(eventArgs.Added); return; } } // 有被删除的墨迹 if (eventArgs.Removed.Count != 0) { if (_currentCommitType == CommitReason.ShapeRecognition) { ReplacedStroke = eventArgs.Removed; return; } else if ((_edittingMode == EditingMode.GeometryErasing || _edittingMode == EditingMode.AreaErasing) || _currentCommitType == CommitReason.ClearingCanvas) { BoardPages.Find(page=>page.GUID==GUID).TimeMachine.CommitStrokeEraseHistory(eventArgs.Removed); return; } } } /// /// 内部的InkCanvas的Stroke触发了Stroke.DrawingAttributesChanged事件 /// /// 发送事件的Stroke(需要用传入的Dispatcher操作) /// 这个InkCanvas所在的Dispatcher对象 /// 这个InkCanvas所存放的DispatcherContainer /// InkCanvas(也是页面的)GUID /// PropertyDataChangedEventArgs事件 private void InnerInkCanvas_Stroke_OnDrawingAttributesChanged(object sender, Dispatcher dispatcher, DispatcherContainer container, Guid GUID, PropertyDataChangedEventArgs eventArgs) { Trace.WriteLine($"GUID:{GUID} 触发了Stroke_OnDrawingAttributesChanged,第{BoardPages.IndexOf(BoardPages.Find(page=>page.GUID.Equals(GUID)))+1}页"); } /// /// 内部的InkCanvas的Stroke触发了Stroke.StylusPointsChanged事件 /// /// 发送事件的Stroke(需要用传入的Dispatcher操作) /// 这个InkCanvas所在的Dispatcher对象 /// 这个InkCanvas所存放的DispatcherContainer /// InkCanvas(也是页面的)GUID /// EventArgs事件 private void InnerInkCanvas_Stroke_OnStylusPointsChanged(object sender, Dispatcher dispatcher, DispatcherContainer container, Guid GUID, EventArgs eventArgs) { Trace.WriteLine($"GUID:{GUID} 触发了Stroke_OnStylusPointsChanged,第{BoardPages.IndexOf(BoardPages.Find(page=>page.GUID.Equals(GUID)))+1}页"); } /// /// 内部的InkCanvas的Stroke触发了Stroke.StylusPointsReplaced事件 /// /// 发送事件的Stroke(需要用传入的Dispatcher操作) /// 这个InkCanvas所在的Dispatcher对象 /// 这个InkCanvas所存放的DispatcherContainer /// InkCanvas(也是页面的)GUID /// StylusPointsReplacedEventArgs事件 private void InnerInkCanvas_Stroke_OnStylusPointsReplaced(object sender, Dispatcher dispatcher, DispatcherContainer container, Guid GUID, StylusPointsReplacedEventArgs eventArgs) { Trace.WriteLine($"GUID:{GUID} 触发了Stroke_OnStylusPointsReplaced,第{BoardPages.IndexOf(BoardPages.Find(page=>page.GUID.Equals(GUID)))+1}页"); } #endregion #region TimeMachine Public APIs /// /// 当前页面的历史记录数量 /// public int CurrentPageHistoriesCount => CurrentPageItem.TimeMachine.CurrentHistoriesCount; /// /// 获取当前页面能否撤销 /// public bool CanUndo => CurrentPageItem.TimeMachine.CanUndo; /// /// 获取当前页面能否重做 /// public bool CanRedo => CurrentPageItem.TimeMachine.CanRedo; /// /// 撤销(仅针对当前激活页面) /// public void Undo() { if (!_isTimeMachineThreadTaskDone) return; var history = CurrentPageItem.TimeMachine.Undo(false); if (history != null) ApplyHistoryToPage(CurrentPageItem,history); } /// /// 多步撤销(仅针对当前激活页面) /// public void Undo(int steps = 1) { if (!_isTimeMachineThreadTaskDone) return; var histories = CurrentPageItem.TimeMachine.Undo(steps); if (histories.Length <= 0) return; foreach (var history in histories) { ApplyHistoryToPage(CurrentPageItem,history); } } /// /// 重做(仅针对当前激活页面) /// public void Redo() { if (!_isTimeMachineThreadTaskDone) return; var history = CurrentPageItem.TimeMachine.Redo(false); if (history != null) ApplyHistoryToPage(CurrentPageItem,history); } /// /// 多步撤销(仅针对当前激活页面) /// public void Redo(int steps = 1) { if (!_isTimeMachineThreadTaskDone) return; var histories = CurrentPageItem.TimeMachine.Redo(steps); if (histories.Length <= 0) return; foreach (var history in histories) { ApplyHistoryToPage(CurrentPageItem,history); } } #endregion #region 漫游相关代码 Roaming private bool isRoamingHitTestBorderPointerDown = false; private Point? roamingFirstPoint; private Point? roamingLastPoint; private Matrix? roamingMatrix = null; private void RoamingHitTestBorder_Loaded(object sender, RoutedEventArgs e) { var rh = (Border)sender; rh.StylusDown += ((o, args) => { e.Handled = true; if (args.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus) ((Border)o).CaptureStylus(); RoamingHitTestBorder_PointerDown(sender); }); rh.StylusUp += ((o, args) => { e.Handled = true; if (args.StylusDevice.TabletDevice.Type == TabletDeviceType.Stylus) ((Border)o).ReleaseStylusCapture(); RoamingHitTestBorder_PointerUp(sender); }); rh.StylusMove += ((o, args) => { e.Handled = true; RoamingHitTestBorder_PointerMove(sender, args.GetPosition(WrapperInkCanvas), args.GetPosition(null)); }); rh.MouseDown += ((o, args) => { ((Border)o).CaptureMouse(); RoamingHitTestBorder_PointerDown(sender); }); rh.MouseUp += ((o, args) => { ((Border)o).ReleaseMouseCapture(); RoamingHitTestBorder_PointerUp(sender); }); rh.MouseMove += ((o, args) => { RoamingHitTestBorder_PointerMove(sender, args.GetPosition(WrapperInkCanvas), args.GetPosition(null)); }); } /// /// TODO: 设置页面的MatrixTransform(还没有正式完工) /// /// /// private void _SetPageMatrixTransform(IccBoardPage page, Matrix matrix) { if (page.Container.RenderTransformOrigin != new Point(0, 0)) page.Container.RenderTransformOrigin = new Point(0, 0); page.Container.RenderTransform = new MatrixTransform(matrix); WrapperInkCanvasMatrixTransform.Matrix = matrix; } private void RoamingHitTestBorder_PointerDown(object sender) { if (isRoamingHitTestBorderPointerDown) return; isRoamingHitTestBorderPointerDown = true; roamingFirstPoint = null; roamingLastPoint = null; roamingMatrix = null; } private void RoamingHitTestBorder_PointerUp(object sender) { if (!isRoamingHitTestBorderPointerDown) return; isRoamingHitTestBorderPointerDown = false; CurrentPageItem.TransformMatrix = (CurrentPageItem.Container.RenderTransform as MatrixTransform).Value; roamingFirstPoint = null; roamingLastPoint = null; roamingMatrix = null; } private void RoamingHitTestBorder_PointerMove(object sender, Point ptInInkCanvas, Point ptAbsolute) { if (!isRoamingHitTestBorderPointerDown) return; if (roamingFirstPoint == null) roamingFirstPoint = ptAbsolute; roamingLastPoint = ptAbsolute; if (roamingMatrix == null) roamingMatrix = CurrentPageItem.TransformMatrix; var deltaX = (roamingLastPoint ?? new Point(0, 0)).X - (roamingFirstPoint ?? new Point(0, 0)).X; var deltaY = (roamingLastPoint ?? new Point(0, 0)).Y - (roamingFirstPoint ?? new Point(0, 0)).Y; var mt = roamingMatrix??new Matrix(); mt.Translate(deltaX,deltaY); _SetPageMatrixTransform(CurrentPageItem,mt); } #endregion public IccBoard() { InitializeComponent(); } } }