Files
2025-08-23 21:39:21 +08:00

1266 lines
60 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();
}
}
}