1266 lines
60 KiB
C#
1266 lines
60 KiB
C#
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; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 基于InkCanvas封装的墨迹书写控件
|
||
/// </summary>
|
||
public partial class IccBoard : UserControl {
|
||
|
||
public BoardSettings BoardSettings { get; private set; } = new BoardSettings();
|
||
|
||
private List<IccInnerInkCanvasInfo> DispatcherInkCanvasList { get; set; } =
|
||
new List<IccInnerInkCanvasInfo>();
|
||
|
||
private List<IccBoardPage> BoardPages { get; set; } = new List<IccBoardPage>();
|
||
|
||
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);
|
||
}
|
||
/// <summary>
|
||
/// 该事件接收撤销和重做状态变更的事件(注意,当前页面变更也会触发该事件。该事件接收每一页撤销和重做状态变更的事件,但是不提供具体是哪一页的时光机发生了状态变更,可以用这个事件来更新当前页面的时光机UI状态)
|
||
/// </summary>
|
||
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))));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 该事件用于接收时光机状态变更的事件(接收所有页面的时光机状态变化)
|
||
/// </summary>
|
||
public event EventHandler<TimeMachineStatusUpdatedEventArgs> 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
|
||
|
||
/// <summary>
|
||
/// 记录所有已注册了Strokes_OnStrokesChanged事件的InkCanvas的GUID
|
||
/// </summary>
|
||
private Dictionary<Guid, StrokeCollectionChangedEventHandler>
|
||
_innerInkCanvas_Strokes_OnStrokesChanged_wrappers = new Dictionary<Guid, StrokeCollectionChangedEventHandler>();
|
||
|
||
/// <summary>
|
||
/// 记录所有已登记要触发Stroke_OnDrawingAttributesChanged事件的InkCanvas的GUID
|
||
/// (为什么不是已注册?因为这个事件是针对一条Stroke而言的,这里只是记录这个InkCanvas将会调用这个事件)
|
||
/// </summary>
|
||
private Dictionary<Guid, PropertyDataChangedEventHandler>
|
||
_innerInkCanvas_Stroke_OnDrawingAttributesChanged_wrappers = new Dictionary<Guid, PropertyDataChangedEventHandler>();
|
||
|
||
/// <summary>
|
||
/// 记录所有已登记要触发Stroke_OnStylusPointsChanged事件的InkCanvas的GUID
|
||
/// (为什么不是已注册?因为这个事件是针对一条Stroke而言的,这里只是记录这个InkCanvas将会调用这个事件)
|
||
/// </summary>
|
||
private Dictionary<Guid, EventHandler>
|
||
_innerInkCanvas_Stroke_OnStylusPointsChanged_wrappers = new Dictionary<Guid, EventHandler>();
|
||
|
||
/// <summary>
|
||
/// 记录所有已登记要触发Stroke_OnStylusPointsChanged事件的InkCanvas的GUID
|
||
/// (为什么不是已注册?因为这个事件是针对一条Stroke而言的,这里只是记录这个InkCanvas将会调用这个事件)
|
||
/// </summary>
|
||
private Dictionary<Guid, StylusPointsReplacedEventHandler>
|
||
_innerInkCanvas_Stroke_OnStylusPointsReplaced_wrappers = new Dictionary<Guid, StylusPointsReplacedEventHandler>();
|
||
|
||
private async Task<IccInnerInkCanvasInfo> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加时光机支持到DispatcherInkCanvas中(这里是直接以GUID为标识符的)
|
||
/// </summary>
|
||
/// <param name="dispatcher"></param>
|
||
/// <param name="container"></param>
|
||
/// <param name="GUID"></param>
|
||
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);
|
||
});
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 取消注册时光机支持
|
||
/// </summary>
|
||
/// <param name="dispatcher"></param>
|
||
/// <param name="container"></param>
|
||
/// <param name="GUID"></param>
|
||
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
|
||
|
||
/// <summary>
|
||
/// 根据索引值获取页面
|
||
/// </summary>
|
||
/// <param name="index"></param>
|
||
/// <returns></returns>
|
||
public IccBoardPage this[int index] => BoardPages[index];
|
||
|
||
/// <summary>
|
||
/// 根据InkCanvas的GUID获取页面
|
||
/// </summary>
|
||
/// <param name="GUID"></param>
|
||
/// <returns></returns>
|
||
public IccBoardPage this[Guid GUID] => BoardPages.Find(page => page.GUID == GUID);
|
||
|
||
/// <summary>
|
||
/// 获取总页数
|
||
/// </summary>
|
||
public int PagesCount {
|
||
get => BoardPages.Count;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前页面的索引值(如果需要展示页数的话,获取到的还需要加1)
|
||
/// </summary>
|
||
public int CurrentPage {
|
||
get => CurrentPageIndex;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取到当前页面的 IccBoardPage 对象
|
||
/// </summary>
|
||
public IccBoardPage CurrentPageItem {
|
||
get => BoardPages[CurrentPageIndex];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加新的白板页面(同时会创建新的DispatcherInkCanvas,如果想用现有的DispatcherInkCanvas插入为新页面的话,请使用 <see cref="AddPageFromExistedDispatcherInkCanvas"/> )
|
||
/// </summary>
|
||
/// <param name="boardPageAppendMode">选择页面插入的模式</param>
|
||
/// <param name="jumpToPageWhenAdded">指定是否在插入页面后跳转到该页面</param>
|
||
/// <returns></returns>
|
||
public async Task<IccBoardPage> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从现有的DispatcherInkCanvas中添加页面(和AddPage的区别就是不会调用AddIccInkCanvas,不会注册事件,也不会对DispatcherInkCanvas做任何初始化操作)
|
||
/// (注意:如果使用该方法,需要自行处理TimeMachine相关的东西!)
|
||
/// </summary>
|
||
/// <param name="container"></param>
|
||
/// <param name="dispatcher"></param>
|
||
/// <param name="GUID"></param>
|
||
/// <param name="boardPageAppendMode"></param>
|
||
/// <param name="jumpToPageWhenAdded"></param>
|
||
/// <returns></returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换到上一页
|
||
/// </summary>
|
||
/// <param name="step">跳转的页数,默认为1</param>
|
||
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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 切换到下一页
|
||
/// </summary>
|
||
/// <param name="step">跳转的页数,默认为1</param>
|
||
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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 跳转到指定索引的页面
|
||
/// </summary>
|
||
/// <param name="index">索引</param>
|
||
public void GotoPage(int index) {
|
||
UpdateInnerInkCanvasVisibility(BoardPages[index]);
|
||
|
||
if (!index.Equals(CurrentPageIndex)) {
|
||
CurrentPageIndex = index;
|
||
RaiseCurrentPageChangedEvent();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 跳转到指定页面
|
||
/// </summary>
|
||
/// <param name="page">页面</param>
|
||
public void GotoPage(IccBoardPage page) {
|
||
var index = BoardPages.IndexOf(page);
|
||
UpdateInnerInkCanvasVisibility(page);
|
||
|
||
if (!index.Equals(CurrentPageIndex)) {
|
||
CurrentPageIndex = index;
|
||
RaiseCurrentPageChangedEvent();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 指示当前页面是否是最后一页,如果只有一页会返回true
|
||
/// </summary>
|
||
public bool IsCurrentLastPage => CurrentPageIndex == BoardPages.Count - 1;
|
||
|
||
/// <summary>
|
||
/// 指示当前页面是否是第一页,如果只有一页会返回true
|
||
/// </summary>
|
||
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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除指定索引的页面,如果只有1页,执行该方法会删除该页面并新建一个页面。
|
||
/// </summary>
|
||
/// <param name="index">索引</param>
|
||
/// <param name="isDisposeInstance">指定是否销毁该页面的Dispatcher,销毁后无法再使用该实例添加到Board!</param>
|
||
/// <param name="pageSwitchMode">删除当前激活页面后切换到临近页面的行为,如果删除的页面不是当前页面则不会生效</param>
|
||
public async Task RemovePageAt(int index, bool? isDisposeInstance = true, PageSwitchMode? pageSwitchMode = PageSwitchMode.SwitchToPreviousPage) {
|
||
var page = BoardPages[index];
|
||
await _RemovePage(page, isDisposeInstance, pageSwitchMode);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除当前页面,如果只有1页,执行该方法会删除当前页面并新建一个页面。
|
||
/// </summary>
|
||
/// <param name="isDisposeInstance">指定是否销毁该页面的Dispatcher,销毁后无法再使用该实例添加到Board!</param>
|
||
/// <param name="pageSwitchMode">删除当前激活页面后切换到临近页面的行为,如果删除的页面不是当前页面则不会生效</param>
|
||
public async Task RemovePage(bool? isDisposeInstance = true, PageSwitchMode? pageSwitchMode = PageSwitchMode.SwitchToPreviousPage) {
|
||
var page = CurrentPageItem;
|
||
await _RemovePage(page, isDisposeInstance, pageSwitchMode);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移除指定的页面,如果只有1页,执行该方法会删除该页面并新建一个页面。
|
||
/// </summary>
|
||
/// <param name="page">页面</param>
|
||
/// <param name="isDisposeInstance">指定是否销毁该页面的Dispatcher,销毁后无法再使用该实例添加到Board!</param>
|
||
/// <param name="pageSwitchMode">删除当前激活页面后切换到临近页面的行为,如果删除的页面不是当前页面则不会生效</param>
|
||
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<Stroke, Tuple<StylusPointCollection, StylusPointCollection>> StylusPointsChangedHistory;
|
||
|
||
private Dictionary<Stroke, StylusPointCollection> StrokeInitialHistory =
|
||
new Dictionary<Stroke, StylusPointCollection>();
|
||
|
||
private Dictionary<Stroke, Tuple<DrawingAttributes, DrawingAttributes>> DrawingAttributesHistory =
|
||
new Dictionary<Stroke, Tuple<DrawingAttributes, DrawingAttributes>>();
|
||
|
||
private Dictionary<Guid, List<Stroke>> DrawingAttributesHistoryFlag = new Dictionary<Guid, List<Stroke>>() {
|
||
{ DrawingAttributeIds.Color, new List<Stroke>() },
|
||
{ DrawingAttributeIds.DrawingFlags, new List<Stroke>() },
|
||
{ DrawingAttributeIds.IsHighlighter, new List<Stroke>() },
|
||
{ DrawingAttributeIds.StylusHeight, new List<Stroke>() },
|
||
{ DrawingAttributeIds.StylusTip, new List<Stroke>() },
|
||
{ DrawingAttributeIds.StylusTipTransform, new List<Stroke>() },
|
||
{ DrawingAttributeIds.StylusWidth, new List<Stroke>() }
|
||
};
|
||
|
||
/// <summary>
|
||
/// 根据传入的History项目,将历史记录应用到指定页面上去
|
||
/// </summary>
|
||
/// <param name="page"></param>
|
||
/// <param name="history"></param>
|
||
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;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量应用多个历史记录到页面上
|
||
/// </summary>
|
||
/// <param name="page"></param>
|
||
/// <param name="histories"></param>
|
||
private void ApplyHistoriesToPage(IccBoardPage page, TimeMachineHistory[] histories) {
|
||
foreach (var timeMachineHistory in histories) {
|
||
ApplyHistoryToPage(page,timeMachineHistory);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 内部的InkCanvas触发了Strokes.StrokesChanged事件
|
||
/// </summary>
|
||
/// <param name="sender">发送事件的InkCanvas(需要用传入的Dispatcher操作)</param>
|
||
/// <param name="dispatcher">这个InkCanvas所在的Dispatcher对象</param>
|
||
/// <param name="container">这个InkCanvas所存放的DispatcherContainer</param>
|
||
/// <param name="GUID">InkCanvas(也是页面的)GUID</param>
|
||
/// <param name="eventArgs">StrokeCollectionChangedEventArgs事件</param>
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 内部的InkCanvas的Stroke触发了Stroke.DrawingAttributesChanged事件
|
||
/// </summary>
|
||
/// <param name="sender">发送事件的Stroke(需要用传入的Dispatcher操作)</param>
|
||
/// <param name="dispatcher">这个InkCanvas所在的Dispatcher对象</param>
|
||
/// <param name="container">这个InkCanvas所存放的DispatcherContainer</param>
|
||
/// <param name="GUID">InkCanvas(也是页面的)GUID</param>
|
||
/// <param name="eventArgs">PropertyDataChangedEventArgs事件</param>
|
||
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}页");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 内部的InkCanvas的Stroke触发了Stroke.StylusPointsChanged事件
|
||
/// </summary>
|
||
/// <param name="sender">发送事件的Stroke(需要用传入的Dispatcher操作)</param>
|
||
/// <param name="dispatcher">这个InkCanvas所在的Dispatcher对象</param>
|
||
/// <param name="container">这个InkCanvas所存放的DispatcherContainer</param>
|
||
/// <param name="GUID">InkCanvas(也是页面的)GUID</param>
|
||
/// <param name="eventArgs">EventArgs事件</param>
|
||
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}页");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 内部的InkCanvas的Stroke触发了Stroke.StylusPointsReplaced事件
|
||
/// </summary>
|
||
/// <param name="sender">发送事件的Stroke(需要用传入的Dispatcher操作)</param>
|
||
/// <param name="dispatcher">这个InkCanvas所在的Dispatcher对象</param>
|
||
/// <param name="container">这个InkCanvas所存放的DispatcherContainer</param>
|
||
/// <param name="GUID">InkCanvas(也是页面的)GUID</param>
|
||
/// <param name="eventArgs">StylusPointsReplacedEventArgs事件</param>
|
||
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
|
||
|
||
/// <summary>
|
||
/// 当前页面的历史记录数量
|
||
/// </summary>
|
||
public int CurrentPageHistoriesCount => CurrentPageItem.TimeMachine.CurrentHistoriesCount;
|
||
|
||
/// <summary>
|
||
/// 获取当前页面能否撤销
|
||
/// </summary>
|
||
public bool CanUndo => CurrentPageItem.TimeMachine.CanUndo;
|
||
|
||
/// <summary>
|
||
/// 获取当前页面能否重做
|
||
/// </summary>
|
||
public bool CanRedo => CurrentPageItem.TimeMachine.CanRedo;
|
||
|
||
/// <summary>
|
||
/// 撤销(仅针对当前激活页面)
|
||
/// </summary>
|
||
public void Undo() {
|
||
if (!_isTimeMachineThreadTaskDone) return;
|
||
var history = CurrentPageItem.TimeMachine.Undo(false);
|
||
if (history != null) ApplyHistoryToPage(CurrentPageItem,history);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 多步撤销(仅针对当前激活页面)
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 重做(仅针对当前激活页面)
|
||
/// </summary>
|
||
public void Redo() {
|
||
if (!_isTimeMachineThreadTaskDone) return;
|
||
var history = CurrentPageItem.TimeMachine.Redo(false);
|
||
if (history != null) ApplyHistoryToPage(CurrentPageItem,history);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 多步撤销(仅针对当前激活页面)
|
||
/// </summary>
|
||
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));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// TODO: 设置页面的MatrixTransform(还没有正式完工)
|
||
/// </summary>
|
||
/// <param name="page"></param>
|
||
/// <param name="matrix"></param>
|
||
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();
|
||
}
|
||
|
||
}
|
||
}
|