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();
}
}
}