欢迎光临
我们一直在努力

第3章 WPF高级界面组件与数字墨迹绘图技术实战

第3章 WPF高级界面组件与数字墨迹绘图技术实战

3.1 Ribbon界面控件在企业级网络应用中的设计与应用

3.1.1 现代化应用界面选项卡设计策略

在现代C#网络应用开发中,用户界面设计直接影响用户体验和工作效率。Ribbon控件作为微软Office系列引入的界面范式,已成为专业应用程序的标准界面元素。在WPF(Windows Presentation Foundation)中,Ribbon控件提供了一种组织复杂功能的高效方式。

Ribbon控件的基本架构:
Ribbon控件由多个层次组成,包括应用程序菜单、快速访问工具栏、选项卡组和上下文选项卡。这种结构特别适合功能丰富的网络管理工具、监控系统或协作平台。

在WPF中集成Ribbon控件:
首先需要在项目中添加对Ribbon控件的引用。对于.NET Framework项目,需要引用System.Windows.Controls.Ribbon程序集;对于.NET Core/.NET 5+项目,需要使用Windows兼容包或第三方Ribbon控件库。

基础Ribbon界面实现示例:

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Ribbon;
using System.Windows.Media;

namespace NetworkToolRibbonUI
{
public partial class MainWindow : RibbonWindow
{
public MainWindow()
{
InitializeComponent();
SetupNetworkToolRibbon();
}

private void SetupNetworkToolRibbon()
{
// 创建Ribbon控件
Ribbon mainRibbon = new Ribbon();
mainRibbon.Title = "网络工具集 v1.0";

// 设置快速访问工具栏
SetupQuickAccessToolbar(mainRibbon);

// 创建主选项卡
CreateNetworkMonitorTab(mainRibbon);
CreateNetworkScanTab(mainRibbon);
CreateNetworkDiagnosticsTab(mainRibbon);
CreateToolsTab(mainRibbon);

// 添加上下文选项卡(特定条件下显示)
CreateDrawingContextualTab(mainRibbon);

this.Content = mainRibbon;
}

private void SetupQuickAccessToolbar(Ribbon ribbon)
{
// 快速访问工具栏 – 常用功能
ribbon.QuickAccessToolBar = new RibbonQuickAccessToolBar();

var saveButton = new RibbonButton
{
Label = "保存",
SmallImageSource = CreateGeometryImage("M19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7Z"),
ToolTip = "保存当前工作"
};
saveButton.Click += OnSaveClicked;

var openButton = new RibbonButton
{
Label = "打开",
SmallImageSource = CreateGeometryImage("M10,2H14A2,2 0 0,1 16,4V6H20A2,2 0 0,1 22,8V19A2,2 0 0,1 20,21H4C2.89,21 2,20.1 2,19V8C2,6.89 2.89,6 4,6H8V4C8,2.89 8.89,2 10,2M14,6V4H10V6H14Z"),
ToolTip = "打开网络配置文件"
};
openButton.Click += OnOpenClicked;

ribbon.QuickAccessToolBar.Items.Add(saveButton);
ribbon.QuickAccessToolBar.Items.Add(openButton);
}

private void CreateNetworkMonitorTab(Ribbon ribbon)
{
RibbonTab monitorTab = new RibbonTab
{
Header = "网络监控",
KeyTip = "M"
};

// 监控组
RibbonGroup monitorGroup = new RibbonGroup
{
Header = "实时监控"
};

var startMonitorButton = new RibbonButton
{
Label = "开始监控",
LargeImageSource = CreateGeometryImage("M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z"),
ToolTip = "开始网络流量监控"
};
startMonitorButton.Click += OnStartMonitorClicked;

var stopMonitorButton = new RibbonButton
{
Label = "停止监控",
LargeImageSource = CreateGeometryImage("M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M9,9H15V15H9"),
ToolTip = "停止网络监控"
};
stopMonitorButton.Click += OnStopMonitorClicked;

monitorGroup.Items.Add(startMonitorButton);
monitorGroup.Items.Add(stopMonitorButton);

// 视图组
RibbonGroup viewGroup = new RibbonGroup
{
Header = "视图选项"
};

var trafficViewComboBox = new RibbonComboBox
{
Label = "视图模式"
};
trafficViewComboBox.Items.Add(new RibbonGalleryItem { Content = "实时流量图" });
trafficViewComboBox.Items.Add(new RibbonGalleryItem { Content = "历史数据表" });
trafficViewComboBox.Items.Add(new RibbonGalleryItem { Content = "拓扑视图" });
trafficViewComboBox.SelectionChanged += OnTrafficViewChanged;

var autoRefreshToggle = new RibbonToggleButton
{
Label = "自动刷新",
IsChecked = true
};
autoRefreshToggle.Click += OnAutoRefreshToggled;

viewGroup.Items.Add(trafficViewComboBox);
viewGroup.Items.Add(autoRefreshToggle);

monitorTab.Items.Add(monitorGroup);
monitorTab.Items.Add(viewGroup);
ribbon.Items.Add(monitorTab);
}

private void CreateNetworkScanTab(Ribbon ribbon)
{
RibbonTab scanTab = new RibbonTab
{
Header = "网络扫描",
KeyTip = "S"
};

// 扫描组
RibbonGroup scanGroup = new RibbonGroup
{
Header = "扫描操作"
};

var quickScanButton = new RibbonButton
{
Label = "快速扫描",
LargeImageSource = CreateGeometryImage("M17.9,17.39C17.64,16.59 16.89,16 16,16H15V13A1,1 0 0,0 14,12H8V10H10A1,1 0 0,0 11,9V7H13A2,2 0 0,0 15,5V4.59C17.93,5.77 20,8.64 20,12C20,14.08 19.2,15.97 17.9,17.39M11,19.93C7.05,19.44 4,16.08 4,12C4,11.38 4.08,10.78 4.21,10.21L9,15V16A2,2 0 0,0 11,18M10,4H8V6H10M1,1H7L6,2H2V6L1,7V1.5A0.5,0.5 0 0,0 0.5,1H1M23,1H16.5A0.5,0.5 0 0,0 16,1.5V7L15,6V2H11L10,1H23Z"),
ToolTip = "快速扫描网络设备"
};
quickScanButton.Click += OnQuickScanClicked;

var deepScanButton = new RibbonSplitButton
{
Label = "深度扫描",
LargeImageSource = CreateGeometryImage("M10,9A1,1 0 0,1 11,8A1,1 0 0,1 12,9V13.47L13.21,13.6L18.15,15.79C18.68,16.03 19,16.56 19,17.14V21.5C18.97,22.32 18.32,22.97 17.5,23H11C10.62,23 10.26,22.85 10,22.57L5.1,18.37L5.84,17.6C6.03,17.39 6.3,17.28 6.58,17.28H6.8L10,19V9M12,2A7,7 0 0,1 19,9C19,11.38 17.81,13.47 16,14.74V15A1,1 0 0,1 15,16H9A1,1 0 0,1 8,15V14.74C6.19,13.47 5,11.38 5,9A7,7 0 0,1 12,2Z")
};

// 添加深度扫描选项
var deepScanMenu = new RibbonMenu();
deepScanMenu.Items.Add(new RibbonMenuItem { Header = "端口扫描" });
deepScanMenu.Items.Add(new RibbonMenuItem { Header = "漏洞扫描" });
deepScanMenu.Items.Add(new RibbonMenuItem { Header = "服务识别" });
deepScanButton.DropDown = deepScanMenu;

scanGroup.Items.Add(quickScanButton);
scanGroup.Items.Add(deepScanButton);

// 配置组
RibbonGroup configGroup = new RibbonGroup
{
Header = "扫描配置"
};

var ipRangeTextBox = new RibbonTextBox
{
Label = "IP范围",
Text = "192.168.1.1-192.168.1.254"
};
ipRangeTextBox.TextChanged += OnIpRangeChanged;

var timeoutSlider = new RibbonSlider
{
Label = "超时(ms)",
Minimum = 100,
Maximum = 5000,
Value = 1000,
TickFrequency = 100
};
timeoutSlider.ValueChanged += OnTimeoutChanged;

configGroup.Items.Add(ipRangeTextBox);
configGroup.Items.Add(timeoutSlider);

scanTab.Items.Add(scanGroup);
scanTab.Items.Add(configGroup);
ribbon.Items.Add(scanTab);
}

private void CreateNetworkDiagnosticsTab(Ribbon ribbon)
{
RibbonTab diagnosticsTab = new RibbonTab
{
Header = "网络诊断",
KeyTip = "D"
};

// 测试工具组
RibbonGroup testGroup = new RibbonGroup
{
Header = "诊断工具"
};

var pingButton = new RibbonButton
{
Label = "Ping测试",
LargeImageSource = CreateGeometryImage("M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M16.2,16.2L11,13.6V7H12.5V12.8L17,14.9L16.2,16.2Z")
};
pingButton.Click += OnPingTestClicked;

var tracerouteButton = new RibbonButton
{
Label = "路由追踪",
LargeImageSource = CreateGeometryImage("M11,2V22C5.9,21.5 2,17.2 2,12C2,6.8 5.9,2.5 11,2M13,2V11H22C21.5,6.2 17.8,2.5 13,2M13,13V22C17.7,21.5 21.5,17.8 22,13H13Z")
};
tracerouteButton.Click += OnTracerouteClicked;

testGroup.Items.Add(pingButton);
testGroup.Items.Add(tracerouteButton);

// 分析组
RibbonGroup analysisGroup = new RibbonGroup
{
Header = "数据分析"
};

var exportButton = new RibbonButton
{
Label = "导出报告",
LargeImageSource = CreateGeometryImage("M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z")
};
exportButton.Click += OnExportReportClicked;

analysisGroup.Items.Add(exportButton);

diagnosticsTab.Items.Add(testGroup);
diagnosticsTab.Items.Add(analysisGroup);
ribbon.Items.Add(diagnosticsTab);
}

private void CreateToolsTab(Ribbon ribbon)
{
RibbonTab toolsTab = new RibbonTab
{
Header = "工具集",
KeyTip = "T"
};

// 网络工具组
RibbonGroup netToolsGroup = new RibbonGroup
{
Header = "网络工具"
};

var bandwidthTestButton = new RibbonButton
{
Label = "带宽测试",
LargeImageSource = CreateGeometryImage("M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4M12,6A6,6 0 0,0 6,12A6,6 0 0,0 12,18A6,6 0 0,0 18,12A6,6 0 0,0 12,6Z")
};
bandwidthTestButton.Click += OnBandwidthTestClicked;

var dnsLookupButton = new RibbonButton
{
Label = "DNS查询",
LargeImageSource = CreateGeometryImage("M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z")
};
dnsLookupButton.Click += OnDnsLookupClicked;

netToolsGroup.Items.Add(bandwidthTestButton);
netToolsGroup.Items.Add(dnsLookupButton);

// 实用工具组
RibbonGroup utilToolsGroup = new RibbonGroup
{
Header = "实用工具"
};

var calculatorButton = new RibbonButton
{
Label = "子网计算器",
LargeImageSource = CreateGeometryImage("M2,2H22V22H2V2M20,12V20H12V12H20M10,8V4H4V10H8V14H4V20H10V16H14V20H20V14H16V10H20V4H14V8H10M10,10H14V14H10V10Z")
};
calculatorButton.Click += OnSubnetCalculatorClicked;

var converterButton = new RibbonButton
{
Label = "进制转换",
LargeImageSource = CreateGeometryImage("M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M10,7H14V9H11V15H10V7M17,7H19V15H17V7M7,7H9V15H7V7Z")
};
converterButton.Click += OnBaseConverterClicked;

utilToolsGroup.Items.Add(calculatorButton);
utilToolsGroup.Items.Add(converterButton);

toolsTab.Items.Add(netToolsGroup);
toolsTab.Items.Add(utilToolsGroup);
ribbon.Items.Add(toolsTab);
}

private void CreateDrawingContextualTab(Ribbon ribbon)
{
// 上下文选项卡 – 仅在绘图模式下显示
RibbonTab drawingTab = new RibbonTab
{
Header = "绘图工具",
ContextualTabGroupHeader = "绘图",
Visibility = Visibility.Collapsed, // 默认隐藏
KeyTip = "P"
};

// 绘图工具组
RibbonGroup drawingToolsGroup = new RibbonGroup
{
Header = "绘图工具"
};

var penToolButton = new RibbonToggleButton
{
Label = "画笔",
LargeImageSource = CreateGeometryImage("M3,17.25V21H6.75L17.81,9.94L14.06,6.19L3,17.25M21.41,6.34L17.66,2.59L15.13,5.13L18.88,8.88L21.41,6.34Z")
};
penToolButton.Click += OnPenToolSelected;

var shapeToolButton = new RibbonToggleButton
{
Label = "形状",
LargeImageSource = CreateGeometryImage("M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M12,20C7.58,20 4,16.42 4,12C4,7.58 7.58,4 12,4C16.42,4 20,7.58 20,12C20,16.42 16.42,20 12,20M16.18,7.76L15.12,8.82L14.06,7.76L13,8.82L14.06,9.88L13,10.94L14.06,12L15.12,10.94L16.18,12L17.24,10.94L16.18,9.88L17.24,8.82L16.18,7.76M7.82,12L8.88,10.94L9.94,12L11,10.94L9.94,9.88L11,8.82L9.94,7.76L8.88,8.82L7.82,7.76L6.76,8.82L7.82,9.88L6.76,10.94L7.82,12M12,14C9.67,14 7.69,15.46 6.89,17.5H17.11C16.31,15.46 14.33,14 12,14Z")
};
shapeToolButton.Click += OnShapeToolSelected;

drawingToolsGroup.Items.Add(penToolButton);
drawingToolsGroup.Items.Add(shapeToolButton);

// 颜色组
RibbonGroup colorGroup = new RibbonGroup
{
Header = "颜色选项"
};

var colorPicker = new RibbonComboBox
{
Label = "笔刷颜色"
};
colorPicker.Items.Add(new RibbonGalleryItem { Content = "黑色", Background = Brushes.Black });
colorPicker.Items.Add(new RibbonGalleryItem { Content = "红色", Background = Brushes.Red });
colorPicker.Items.Add(new RibbonGalleryItem { Content = "蓝色", Background = Brushes.Blue });
colorPicker.Items.Add(new RibbonGalleryItem { Content = "绿色", Background = Brushes.Green });
colorPicker.SelectionChanged += OnColorSelected;

var thicknessSlider = new RibbonSlider
{
Label = "线宽",
Minimum = 1,
Maximum = 20,
Value = 3,
TickFrequency = 1
};
thicknessSlider.ValueChanged += OnThicknessChanged;

colorGroup.Items.Add(colorPicker);
colorGroup.Items.Add(thicknessSlider);

drawingTab.Items.Add(drawingToolsGroup);
drawingTab.Items.Add(colorGroup);
ribbon.Items.Add(drawingTab);
}

// 辅助方法:创建几何图形作为图标
private ImageSource CreateGeometryImage(string pathData)
{
var geometry = Geometry.Parse(pathData);
var drawing = new GeometryDrawing(Brushes.Black, new Pen(Brushes.Black, 1), geometry);
var drawingImage = new DrawingImage(drawing);
drawingImage.Freeze();
return drawingImage;
}

// 事件处理方法
private void OnSaveClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("保存功能被调用", "信息", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnOpenClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("打开功能被调用", "信息", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnStartMonitorClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("开始网络监控", "信息", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnStopMonitorClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("停止网络监控", "信息", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnTrafficViewChanged(object sender, SelectionChangedEventArgs e)
{
var comboBox = sender as RibbonComboBox;
if (comboBox != null && comboBox.SelectedItem != null)
{
var item = comboBox.SelectedItem as RibbonGalleryItem;
MessageBox.Show($"切换到视图: {item.Content}", "视图切换", MessageBoxButton.OK, MessageBoxImage.Information);
}
}

private void OnAutoRefreshToggled(object sender, RoutedEventArgs e)
{
var toggle = sender as RibbonToggleButton;
MessageBox.Show($"自动刷新: {(toggle.IsChecked == true ? "启用" : "禁用")}", "设置", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnQuickScanClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("开始快速网络扫描", "扫描", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnIpRangeChanged(object sender, TextChangedEventArgs e)
{
var textBox = sender as RibbonTextBox;
MessageBox.Show($"IP范围更新为: {textBox.Text}", "配置", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnTimeoutChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
var slider = sender as RibbonSlider;
MessageBox.Show($"超时时间设置为: {slider.Value}ms", "配置", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnPingTestClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("开始Ping测试", "诊断", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnTracerouteClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("开始路由追踪", "诊断", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnExportReportClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("导出诊断报告", "报告", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnBandwidthTestClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("开始带宽测试", "工具", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnDnsLookupClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("开始DNS查询", "工具", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnSubnetCalculatorClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("打开子网计算器", "工具", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnBaseConverterClicked(object sender, RoutedEventArgs e)
{
MessageBox.Show("打开进制转换器", "工具", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnPenToolSelected(object sender, RoutedEventArgs e)
{
MessageBox.Show("选择画笔工具", "绘图", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnShapeToolSelected(object sender, RoutedEventArgs e)
{
MessageBox.Show("选择形状工具", "绘图", MessageBoxButton.OK, MessageBoxImage.Information);
}

private void OnColorSelected(object sender, SelectionChangedEventArgs e)
{
var comboBox = sender as RibbonComboBox;
if (comboBox != null && comboBox.SelectedItem != null)
{
var item = comboBox.SelectedItem as RibbonGalleryItem;
MessageBox.Show($"选择颜色: {item.Content}", "绘图", MessageBoxButton.OK, MessageBoxImage.Information);
}
}

private void OnThicknessChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
var slider = sender as RibbonSlider;
MessageBox.Show($"线宽设置为: {slider.Value}", "绘图", MessageBoxButton.OK, MessageBoxImage.Information);
}
}

// 应用程序入口点
public class Program
{
[STAThread]
public static void Main()
{
Application app = new Application();
MainWindow mainWindow = new MainWindow();
mainWindow.Title = "网络工具集 – Ribbon界面示例";
mainWindow.Width = 1024;
mainWindow.Height = 768;
app.Run(mainWindow);
}
}
}

3.1.2 多选项卡功能复用与动态界面管理

在复杂的企业级网络应用中,经常需要在不同上下文环境中复用相同的功能组件。WPF Ribbon控件提供了强大的功能复用机制,允许开发者在多个选项卡中共享相同的命令和UI元素。

功能复用的设计模式:

  • 命令模式(Command Pattern):将操作逻辑封装在命令对象中,多个UI元素绑定到同一命令
  • 资源共享(Resource Sharing):在应用程序级别定义样式、模板和资源
  • 动态界面生成:根据运行时条件动态创建和配置Ribbon元素
  • 网络工具功能复用示例:

    using System;
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Ribbon;
    using System.Windows.Input;
    using System.Windows.Media;

    namespace NetworkToolRibbonAdvanced
    {
    // 自定义命令类 – 实现网络操作命令
    public class NetworkCommand : ICommand
    {
    private readonly Action<object> executeAction;
    private readonly Func<object, bool> canExecuteFunc;

    public event EventHandler CanExecuteChanged;

    public NetworkCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
    executeAction = execute ?? throw new ArgumentNullException(nameof(execute));
    canExecuteFunc = canExecute;
    }

    public bool CanExecute(object parameter)
    {
    return canExecuteFunc == null || canExecuteFunc(parameter);
    }

    public void Execute(object parameter)
    {
    executeAction(parameter);
    }

    public void RaiseCanExecuteChanged()
    {
    CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
    }

    // 网络操作管理器
    public class NetworkOperationManager
    {
    private static NetworkOperationManager instance;
    public static NetworkOperationManager Instance => instance ??= new NetworkOperationManager();

    // 共享的命令
    public ICommand SaveCommand { get; }
    public ICommand OpenCommand { get; }
    public ICommand ExportCommand { get; }
    public ICommand ScanCommand { get; }

    private NetworkOperationManager()
    {
    // 初始化共享命令
    SaveCommand = new NetworkCommand(ExecuteSave, CanExecuteSave);
    OpenCommand = new NetworkCommand(ExecuteOpen);
    ExportCommand = new NetworkCommand(ExecuteExport);
    ScanCommand = new NetworkCommand(ExecuteScan, CanExecuteScan);
    }

    private bool CanExecuteSave(object parameter)
    {
    // 检查是否有需要保存的数据
    return true; // 简化实现
    }

    private void ExecuteSave(object parameter)
    {
    MessageBox.Show("保存网络配置", "保存操作", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    private void ExecuteOpen(object parameter)
    {
    MessageBox.Show("打开网络配置文件", "打开操作", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    private void ExecuteExport(object parameter)
    {
    string format = parameter as string ?? "PDF";
    MessageBox.Show($"导出为 {format} 格式", "导出操作", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    private bool CanExecuteScan(object parameter)
    {
    // 检查网络连接状态
    return System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable();
    }

    private void ExecuteScan(object parameter)
    {
    string scanType = parameter as string ?? "快速扫描";
    MessageBox.Show($"执行 {scanType}", "扫描操作", MessageBoxButton.OK, MessageBoxImage.Information);
    }
    }

    // 动态Ribbon界面生成器
    public class DynamicRibbonBuilder
    {
    private readonly Ribbon ribbon;
    private readonly Dictionary<string, RibbonTab> tabCache = new Dictionary<string, RibbonTab>();

    public DynamicRibbonBuilder(Ribbon targetRibbon)
    {
    ribbon = targetRibbon;
    }

    // 创建或获取选项卡
    public RibbonTab GetOrCreateTab(string tabName, string header, string keyTip = null)
    {
    if (tabCache.TryGetValue(tabName, out RibbonTab existingTab))
    {
    return existingTab;
    }

    RibbonTab newTab = new RibbonTab
    {
    Header = header,
    KeyTip = keyTip ?? GetKeyTip(tabName)
    };

    ribbon.Items.Add(newTab);
    tabCache[tabName] = newTab;

    return newTab;
    }

    // 添加共享功能组
    public RibbonGroup AddSharedFunctionsGroup(RibbonTab tab, string groupName)
    {
    RibbonGroup sharedGroup = new RibbonGroup
    {
    Header = groupName
    };

    // 添加共享的保存按钮
    var saveButton = CreateSharedButton("保存",
    NetworkOperationManager.Instance.SaveCommand,
    "保存当前工作");

    // 添加共享的打开按钮
    var openButton = CreateSharedButton("打开",
    NetworkOperationManager.Instance.OpenCommand,
    "打开配置文件");

    // 添加共享的导出按钮(带下拉菜单)
    var exportSplitButton = new RibbonSplitButton
    {
    Label = "导出",
    Command = NetworkOperationManager.Instance.ExportCommand,
    CommandParameter = "PDF",
    LargeImageSource = CreateGeometryImage("M19,21H5A2,2 0 0,1 3,19V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21M12,7A5,5 0 0,0 7,12A5,5 0 0,0 12,17A5,5 0 0,0 17,12A5,5 0 0,0 12,7Z")
    };

    // 导出格式选项
    var exportMenu = new RibbonMenu();
    var pdfItem = new RibbonMenuItem
    {
    Header = "PDF格式",
    Command = NetworkOperationManager.Instance.ExportCommand,
    CommandParameter = "PDF"
    };
    var excelItem = new RibbonMenuItem
    {
    Header = "Excel格式",
    Command = NetworkOperationManager.Instance.ExportCommand,
    CommandParameter = "Excel"
    };
    var csvItem = new RibbonMenuItem
    {
    Header = "CSV格式",
    Command = NetworkOperationManager.Instance.ExportCommand,
    CommandParameter = "CSV"
    };

    exportMenu.Items.Add(pdfItem);
    exportMenu.Items.Add(excelItem);
    exportMenu.Items.Add(csvItem);
    exportSplitButton.DropDown = exportMenu;

    sharedGroup.Items.Add(saveButton);
    sharedGroup.Items.Add(openButton);
    sharedGroup.Items.Add(exportSplitButton);

    tab.Items.Add(sharedGroup);
    return sharedGroup;
    }

    // 创建网络扫描组(在不同选项卡中复用)
    public RibbonGroup AddNetworkScanGroup(RibbonTab tab, string groupHeader)
    {
    RibbonGroup scanGroup = new RibbonGroup
    {
    Header = groupHeader
    };

    // 快速扫描按钮
    var quickScanButton = new RibbonButton
    {
    Label = "快速扫描",
    Command = NetworkOperationManager.Instance.ScanCommand,
    CommandParameter = "快速扫描",
    LargeImageSource = CreateGeometryImage("M17.9,17.39C17.64,16.59 16.89,16 16,16H15V13A1,1 0 0,0 14,12H8V10H10A1,1 0 0,0 11,9V7H13A2,2 0 0,0 15,5V4.59C17.93,5.77 20,8.64 20,12C20,14.08 19.2,15.97 17.9,17.39M11,19.93C7.05,19.44 4,16.08 4,12C4,11.38 4.08,10.78 4.21,10.21L9,15V16A2,2 0 0,0 11,18M10,4H8V6H10M1,1H7L6,2H2V6L1,7V1.5A0.5,0.5 0 0,0 0.5,1H1M23,1H16.5A0.5,0.5 0 0,0 16,1.5V7L15,6V2H11L10,1H23Z")
    };

    // 深度扫描按钮
    var deepScanButton = new RibbonSplitButton
    {
    Label = "深度扫描",
    LargeImageSource = CreateGeometryImage("M10,9A1,1 0 0,1 11,8A1,1 0 0,1 12,9V13.47L13.21,13.6L18.15,15.79C18.68,16.03 19,16.56 19,17.14V21.5C18.97,22.32 18.32,22.97 17.5,23H11C10.62,23 10.26,22.85 10,22.57L5.1,18.37L5.84,17.6C6.03,17.39 6.3,17.28 6.58,17.28H6.8L10,19V9M12,2A7,7 0 0,1 19,9C19,11.38 17.81,13.47 16,14.74V15A1,1 0 0,1 15,16H9A1,1 0 0,1 8,15V14.74C6.19,13.47 5,11.38 5,9A7,7 0 0,1 12,2Z")
    };

    // 深度扫描选项
    var deepScanMenu = new RibbonMenu();
    var portScanItem = new RibbonMenuItem
    {
    Header = "端口扫描",
    Command = NetworkOperationManager.Instance.ScanCommand,
    CommandParameter = "端口扫描"
    };
    var vulnScanItem = new RibbonMenuItem
    {
    Header = "漏洞扫描",
    Command = NetworkOperationManager.Instance.ScanCommand,
    CommandParameter = "漏洞扫描"
    };

    deepScanMenu.Items.Add(portScanItem);
    deepScanMenu.Items.Add(vulnScanItem);
    deepScanButton.DropDown = deepScanMenu;

    scanGroup.Items.Add(quickScanButton);
    scanGroup.Items.Add(deepScanButton);

    tab.Items.Add(scanGroup);
    return scanGroup;
    }

    // 动态添加工具组(根据用户权限)
    public void AddToolsBasedOnPermission(RibbonTab tab, UserPermission permission)
    {
    if (permission.HasFlag(UserPermission.NetworkAdmin))
    {
    AddAdminToolsGroup(tab);
    }

    if (permission.HasFlag(UserPermission.SecurityAnalyst))
    {
    AddSecurityToolsGroup(tab);
    }

    if (permission.HasFlag(UserPermission.NetworkOperator))
    {
    AddOperatorToolsGroup(tab);
    }
    }

    private void AddAdminToolsGroup(RibbonTab tab)
    {
    RibbonGroup adminGroup = new RibbonGroup
    {
    Header = "管理员工具"
    };

    var configManagerButton = new RibbonButton
    {
    Label = "配置管理",
    LargeImageSource = CreateGeometryImage("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.22,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.22,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.68 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z")
    };
    configManagerButton.Click += OnConfigManagerClicked;

    var userManagerButton = new RibbonButton
    {
    Label = "用户管理",
    LargeImageSource = CreateGeometryImage("M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,14C16.42,14 20,15.79 20,18V20H4V18C4,15.79 7.58,14 12,14Z")
    };
    userManagerButton.Click += OnUserManagerClicked;

    adminGroup.Items.Add(configManagerButton);
    adminGroup.Items.Add(userManagerButton);
    tab.Items.Add(adminGroup);
    }

    private void AddSecurityToolsGroup(RibbonTab tab)
    {
    RibbonGroup securityGroup = new RibbonGroup
    {
    Header = "安全工具"
    };

    var firewallButton = new RibbonButton
    {
    Label = "防火墙管理",
    LargeImageSource = CreateGeometryImage("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z")
    };
    firewallButton.Click += OnFirewallManagerClicked;

    var logAnalyzerButton = new RibbonButton
    {
    Label = "日志分析",
    LargeImageSource = CreateGeometryImage("M3,22V8H7V22H3M10,22V2H14V22H10M17,22V14H21V22H17Z")
    };
    logAnalyzerButton.Click += OnLogAnalyzerClicked;

    securityGroup.Items.Add(firewallButton);
    securityGroup.Items.Add(logAnalyzerButton);
    tab.Items.Add(securityGroup);
    }

    private void AddOperatorToolsGroup(RibbonTab tab)
    {
    RibbonGroup operatorGroup = new RibbonGroup
    {
    Header = "操作员工具"
    };

    var monitorDashboardButton = new RibbonButton
    {
    Label = "监控面板",
    LargeImageSource = CreateGeometryImage("M21,16V4H3V16H21M21,2A2,2 0 0,1 23,4V16A2,2 0 0,1 21,18H14L16,21V22H8V21L10,18H3A2,2 0 0,1 1,16V4A2,2 0 0,1 3,2H21Z")
    };
    monitorDashboardButton.Click += OnMonitorDashboardClicked;

    var alertManagerButton = new RibbonButton
    {
    Label = "告警管理",
    LargeImageSource = CreateGeometryImage("M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z")
    };
    alertManagerButton.Click += OnAlertManagerClicked;

    operatorGroup.Items.Add(monitorDashboardButton);
    operatorGroup.Items.Add(alertManagerButton);
    tab.Items.Add(operatorGroup);
    }

    // 辅助方法
    private RibbonButton CreateSharedButton(string label, ICommand command, string tooltip)
    {
    return new RibbonButton
    {
    Label = label,
    Command = command,
    ToolTip = tooltip,
    LargeImageSource = GetIconForCommand(label)
    };
    }

    private ImageSource GetIconForCommand(string commandLabel)
    {
    // 根据命令标签返回相应的图标
    return commandLabel switch
    {
    "保存" => CreateGeometryImage("M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z"),
    "打开" => CreateGeometryImage("M9,16V10H5L12,3L19,10H15V16H9M5,20V18H19V20H5Z"),
    "导出" => CreateGeometryImage("M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M12,5A7,7 0 0,1 19,12H14V7L9,12L14,17V14H19A7,7 0 0,1 12,5Z"),
    _ => CreateGeometryImage("M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M15,18V16H6V18H15M18,14V12H6V14H18Z")
    };
    }

    private string GetKeyTip(string tabName)
    {
    return tabName.Length > 0 ? tabName[0].ToString().ToUpper() : "X";
    }

    private ImageSource CreateGeometryImage(string pathData)
    {
    var geometry = Geometry.Parse(pathData);
    var drawing = new GeometryDrawing(Brushes.Black, new Pen(Brushes.Black, 1), geometry);
    var drawingImage = new DrawingImage(drawing);
    drawingImage.Freeze();
    return drawingImage;
    }

    // 事件处理方法
    private void OnConfigManagerClicked(object sender, RoutedEventArgs e)
    {
    MessageBox.Show("打开配置管理器", "管理员工具", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    private void OnUserManagerClicked(object sender, RoutedEventArgs e)
    {
    MessageBox.Show("打开用户管理器", "管理员工具", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    private void OnFirewallManagerClicked(object sender, RoutedEventArgs e)
    {
    MessageBox.Show("打开防火墙管理器", "安全工具", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    private void OnLogAnalyzerClicked(object sender, RoutedEventArgs e)
    {
    MessageBox.Show("打开日志分析器", "安全工具", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    private void OnMonitorDashboardClicked(object sender, RoutedEventArgs e)
    {
    MessageBox.Show("打开监控面板", "操作员工具", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    private void OnAlertManagerClicked(object sender, RoutedEventArgs e)
    {
    MessageBox.Show("打开告警管理器", "操作员工具", MessageBoxButton.OK, MessageBoxImage.Information);
    }
    }

    // 用户权限枚举
    [Flags]
    public enum UserPermission
    {
    None = 0,
    NetworkOperator = 1,
    SecurityAnalyst = 2,
    NetworkAdmin = 4,
    All = NetworkOperator | SecurityAnalyst | NetworkAdmin
    }

    // 主窗口
    public partial class AdvancedMainWindow : RibbonWindow
    {
    private DynamicRibbonBuilder ribbonBuilder;
    private UserPermission currentUserPermission = UserPermission.NetworkOperator;

    public AdvancedMainWindow()
    {
    InitializeComponent();
    SetupDynamicRibbon();
    }

    private void SetupDynamicRibbon()
    {
    Ribbon mainRibbon = new Ribbon();
    mainRibbon.Title = "动态网络工具集";

    ribbonBuilder = new DynamicRibbonBuilder(mainRibbon);

    // 根据用户角色创建不同的界面
    CreateUserSpecificInterface(mainRibbon);

    // 添加上下文菜单
    AddContextMenu(mainRibbon);

    this.Content = mainRibbon;
    }

    private void CreateUserSpecificInterface(Ribbon ribbon)
    {
    // 监控选项卡(所有用户都有)
    var monitorTab = ribbonBuilder.GetOrCreateTab("Monitor", "网络监控", "M");
    ribbonBuilder.AddSharedFunctionsGroup(monitorTab, "常用操作");

    // 根据权限添加工具
    ribbonBuilder.AddToolsBasedOnPermission(monitorTab, currentUserPermission);

    // 扫描选项卡
    var scanTab = ribbonBuilder.GetOrCreateTab("Scan", "网络扫描", "S");
    ribbonBuilder.AddNetworkScanGroup(scanTab, "扫描操作");
    ribbonBuilder.AddSharedFunctionsGroup(scanTab, "文件操作");

    // 如果用户有管理员权限,添加管理选项卡
    if (currentUserPermission.HasFlag(UserPermission.NetworkAdmin))
    {
    var adminTab = ribbonBuilder.GetOrCreateTab("Admin", "系统管理", "A");
    ribbonBuilder.AddToolsBasedOnPermission(adminTab, UserPermission.NetworkAdmin);
    }

    // 如果用户有安全分析权限,添加安全选项卡
    if (currentUserPermission.HasFlag(UserPermission.SecurityAnalyst))
    {
    var securityTab = ribbonBuilder.GetOrCreateTab("Security", "安全管理", "E");
    ribbonBuilder.AddToolsBasedOnPermission(securityTab, UserPermission.SecurityAnalyst);
    }
    }

    private void AddContextMenu(Ribbon ribbon)
    {
    // 添加快捷访问工具栏自定义项
    var customizeMenu = new RibbonMenuItem
    {
    Header = "自定义快速访问工具栏"
    };
    customizeMenu.Click += OnCustomizeQuickAccessToolbar;

    // 添加选项卡管理
    var tabManagerMenu = new RibbonMenuItem
    {
    Header = "管理选项卡"
    };
    tabManagerMenu.Click += OnManageTabs;

    var contextMenu = new RibbonContextMenu();
    contextMenu.Items.Add(customizeMenu);
    contextMenu.Items.Add(new Separator());
    contextMenu.Items.Add(tabManagerMenu);

    ribbon.ContextMenu = contextMenu;
    }

    private void OnCustomizeQuickAccessToolbar(object sender, RoutedEventArgs e)
    {
    MessageBox.Show("自定义快速访问工具栏", "界面设置", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    private void OnManageTabs(object sender, RoutedEventArgs e)
    {
    MessageBox.Show("管理选项卡", "界面设置", MessageBoxButton.OK, MessageBoxImage.Information);
    }

    // 模拟用户权限切换
    public void SwitchUserPermission(UserPermission newPermission)
    {
    currentUserPermission = newPermission;

    // 重新创建界面
    Ribbon currentRibbon = this.Content as Ribbon;
    if (currentRibbon != null)
    {
    currentRibbon.Items.Clear();
    CreateUserSpecificInterface(currentRibbon);
    }
    }
    }

    // 应用程序
    public class AdvancedProgram
    {
    [STAThread]
    public static void Main()
    {
    Application app = new Application();

    AdvancedMainWindow mainWindow = new AdvancedMainWindow();
    mainWindow.Title = "动态Ribbon界面示例";
    mainWindow.Width = 1200;
    mainWindow.Height = 800;

    // 创建测试按钮面板
    StackPanel testPanel = new StackPanel
    {
    Orientation = Orientation.Horizontal,
    VerticalAlignment = VerticalAlignment.Bottom,
    HorizontalAlignment = HorizontalAlignment.Center,
    Margin = new Thickness(0, 0, 0, 20)
    };

    // 添加权限切换按钮
    var operatorButton = new Button
    {
    Content = "操作员视图",
    Margin = new Thickness(5),
    Padding = new Thickness(10, 5, 10, 5)
    };
    operatorButton.Click += (s, e) => mainWindow.SwitchUserPermission(UserPermission.NetworkOperator);

    var analystButton = new Button
    {
    Content = "安全分析视图",
    Margin = new Thickness(5),
    Padding = new Thickness(10, 5, 10, 5)
    };
    analystButton.Click += (s, e) => mainWindow.SwitchUserPermission(UserPermission.SecurityAnalyst);

    var adminButton = new Button
    {
    Content = "管理员视图",
    Margin = new Thickness(5),
    Padding = new Thickness(10, 5, 10, 5)
    };
    adminButton.Click += (s, e) => mainWindow.SwitchUserPermission(UserPermission.NetworkAdmin);

    testPanel.Children.Add(operatorButton);
    testPanel.Children.Add(analystButton);
    testPanel.Children.Add(adminButton);

    // 创建主容器
    Grid mainGrid = new Grid();
    mainGrid.Children.Add(mainWindow.Content as UIElement);
    mainGrid.Children.Add(testPanel);

    Window containerWindow = new Window
    {
    Title = "动态Ribbon界面测试",
    Content = mainGrid,
    Width = 1200,
    Height = 850
    };

    app.Run(containerWindow);
    }
    }
    }

    3.2 WPF数字墨迹技术在网络应用中的实现与应用

    3.2.1 墨迹画板基础与网络白板应用

    数字墨迹技术在现代网络应用中扮演着重要角色,特别是在协作工具、远程教学系统和网络会议应用中。WPF提供了强大的InkCanvas控件,支持高质量的笔迹输入和渲染。

    InkCanvas核心功能:

  • 笔迹采集:支持鼠标、触控笔和触摸输入
  • 实时渲染:高质量的反锯齿笔迹显示
  • 编辑操作:选择、移动、删除、缩放笔迹
  • 手势识别:内置常见手势识别
  • 数据持久化:支持笔迹的保存和加载
  • 基础网络白板实现:

    using System;
    using System.IO;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;

    namespace NetworkWhiteboardBasic
    {
    public partial class BasicWhiteboardWindow : Window
    {
    private InkCanvas inkCanvas;
    private ComboBox penColorComboBox;
    private Slider penSizeSlider;
    private ComboBox editingModeComboBox;
    private string currentFileName;

    public BasicWhiteboardWindow()
    {
    InitializeComponent();
    SetupWhiteboard();
    SetupToolbar();
    }

    private void SetupWhiteboard()
    {
    // 创建主网格
    Grid mainGrid = new Grid();

    // 定义行
    mainGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(40) });
    mainGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
    mainGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(30) });

    // 创建墨迹画板
    inkCanvas = new InkCanvas
    {
    Background = Brushes.White,
    DefaultDrawingAttributes = new DrawingAttributes
    {
    Color = Colors.Black,
    Width = 3,
    Height = 3,
    FitToCurve = true,
    IsHighlighter = false,
    IgnorePressure = false,
    StylusTip = StylusTip.Ellipse
    },
    EditingMode = InkCanvasEditingMode.Ink
    };

    // 设置手势识别
    inkCanvas.SetEnabledGestures(new[]
    {
    ApplicationGesture.Circle,
    ApplicationGesture.Check,
    ApplicationGesture.ChevronDown,
    ApplicationGesture.ChevronLeft,
    ApplicationGesture.ChevronRight,
    ApplicationGesture.ChevronUp,
    ApplicationGesture.Curlicue,
    ApplicationGesture.DoubleCurlicue,
    ApplicationGesture.DoubleTap,
    ApplicationGesture.Down,
    ApplicationGesture.DownLeft,
    ApplicationGesture.DownRight,
    ApplicationGesture.Exclamation,
    ApplicationGesture.Left,
    ApplicationGesture.Right,
    ApplicationGesture.ScratchOut,
    ApplicationGesture.Square,
    ApplicationGesture.Star,
    ApplicationGesture.Tap,
    ApplicationGesture.Triangle,
    ApplicationGesture.Up,
    ApplicationGesture.UpLeft,
    ApplicationGesture.UpRight
    });

    // 处理手势事件
    inkCanvas.Gesture += OnInkCanvasGesture;

    // 处理墨迹收集事件
    inkCanvas.StrokeCollected += OnStrokeCollected;
    inkCanvas.StrokeErased += OnStrokeErased;
    inkCanvas.StrokeErasing += OnStrokeErasing;

    Grid.SetRow(inkCanvas, 1);
    mainGrid.Children.Add(inkCanvas);

    this.Content = mainGrid;
    }

    private void SetupToolbar()
    {
    // 创建工具栏面板
    StackPanel toolbarPanel = new StackPanel
    {
    Orientation = Orientation.Horizontal,
    Background = Brushes.LightGray,
    Margin = new Thickness(5)
    };

    // 笔颜色选择
    penColorComboBox = new ComboBox
    {
    Width = 100,
    Margin = new Thickness(5, 0, 5, 0),
    ItemsSource = new[]
    {
    new { Name = "黑色", Color = Colors.Black },
    new { Name = "红色", Color = Colors.Red },
    new { Name = "蓝色", Color = Colors.Blue },
    new { Name = "绿色", Color = Colors.Green },
    new { Name = "黄色", Color = Colors.Yellow },
    new { Name = "紫色", Color = Colors.Purple },
    new { Name = "橙色", Color = Colors.Orange }
    },
    DisplayMemberPath = "Name",
    SelectedIndex = 0
    };
    penColorComboBox.SelectionChanged += OnPenColorChanged;

    // 笔粗细调整
    penSizeSlider = new Slider
    {
    Width = 100,
    Minimum = 1,
    Maximum = 20,
    Value = 3,
    Margin = new Thickness(5, 0, 5, 0)
    };
    penSizeSlider.ValueChanged += OnPenSizeChanged;

    // 编辑模式选择
    editingModeComboBox = new ComboBox
    {
    Width = 120,
    Margin = new Thickness(5, 0, 5, 0),
    ItemsSource = new[]
    {
    new { Mode = InkCanvasEditingMode.Ink, Name = "绘图模式" },
    new { Mode = InkCanvasEditingMode.Select, Name = "选择模式" },
    new { Mode = InkCanvasEditingMode.EraseByPoint, Name = "点擦除" },
    new { Mode = InkCanvasEditingMode.EraseByStroke, Name = "笔画擦除" },
    new { Mode = InkCanvasEditingMode.None, Name = "无操作" }
    },
    DisplayMemberPath = "Name",
    SelectedIndex = 0
    };
    editingModeComboBox.SelectionChanged += OnEditingModeChanged;

    // 操作按钮
    var clearButton = new Button
    {
    Content = "清空画板",
    Margin = new Thickness(5, 0, 5, 0),
    Padding = new Thickness(10, 2, 10, 2)
    };
    clearButton.Click += OnClearButtonClicked;

    var saveButton = new Button
    {
    Content = "保存墨迹",
    Margin = new Thickness(5, 0, 5, 0),
    Padding = new Thickness(10, 2, 10, 2)
    };
    saveButton.Click += OnSaveButtonClicked;

    var loadButton = new Button
    {
    Content = "加载墨迹",
    Margin = new Thickness(5, 0, 5, 0),
    Padding = new Thickness(10, 2, 10, 2)
    };
    loadButton.Click += OnLoadButtonClicked;

    var undoButton = new Button
    {
    Content = "撤销",
    Margin = new Thickness(5, 0, 5, 0),
    Padding = new Thickness(10, 2, 10, 2)
    };
    undoButton.Click += OnUndoButtonClicked;

    var redoButton = new Button
    {
    Content = "重做",
    Margin = new Thickness(5, 0, 5, 0),
    Padding = new Thickness(10, 2, 10, 2)
    };
    redoButton.Click += OnRedoButtonClicked;

    // 将工具栏添加到网格
    toolbarPanel.Children.Add(new Label { Content = "颜色:", VerticalAlignment = VerticalAlignment.Center });
    toolbarPanel.Children.Add(penColorComboBox);
    toolbarPanel.Children.Add(new Label { Content = "粗细:", VerticalAlignment = VerticalAlignment.Center });
    toolbarPanel.Children.Add(penSizeSlider);
    toolbarPanel.Children.Add(new Label { Content = "模式:", VerticalAlignment = VerticalAlignment.Center });
    toolbarPanel.Children.Add(editingModeComboBox);
    toolbarPanel.Children.Add(clearButton);
    toolbarPanel.Children.Add(saveButton);
    toolbarPanel.Children.Add(loadButton);
    toolbarPanel.Children.Add(undoButton);
    toolbarPanel.Children.Add(redoButton);

    Grid.SetRow(toolbarPanel, 0);
    ((Grid)this.Content).Children.Add(toolbarPanel);

    // 创建状态栏
    TextBlock statusBar = new TextBlock
    {
    Text = "就绪",
    Background = Brushes.LightGray,
    Padding = new Thickness(5),
    VerticalAlignment = VerticalAlignment.Center
    };

    Grid.SetRow(statusBar, 2);
    ((Grid)this.Content).Children.Add(statusBar);
    }

    // 事件处理方法
    private void OnPenColorChanged(object sender, SelectionChangedEventArgs e)
    {
    if (penColorComboBox.SelectedItem != null)
    {
    dynamic selectedItem = penColorComboBox.SelectedItem;
    Color selectedColor = selectedItem.Color;

    DrawingAttributes drawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone();
    drawingAttributes.Color = selectedColor;
    inkCanvas.DefaultDrawingAttributes = drawingAttributes;
    }
    }

    private void OnPenSizeChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
    DrawingAttributes drawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone();
    drawingAttributes.Width = penSizeSlider.Value;
    drawingAttributes.Height = penSizeSlider.Value;
    inkCanvas.DefaultDrawingAttributes = drawingAttributes;
    }

    private void OnEditingModeChanged(object sender, SelectionChangedEventArgs e)
    {
    if (editingModeComboBox.SelectedItem != null)
    {
    dynamic selectedItem = editingModeComboBox.SelectedItem;
    inkCanvas.EditingMode = selectedItem.Mode;
    }
    }

    private void OnClearButtonClicked(object sender, RoutedEventArgs e)
    {
    inkCanvas.Strokes.Clear();
    UpdateStatus("画板已清空");
    }

    private void OnSaveButtonClicked(object sender, RoutedEventArgs e)
    {
    Microsoft.Win32.SaveFileDialog saveFileDialog = new Microsoft.Win32.SaveFileDialog
    {
    Filter = "墨迹文件 (*.isf)|*.isf|所有文件 (*.*)|*.*",
    DefaultExt = ".isf",
    FileName = currentFileName ?? "墨迹画板"
    };

    if (saveFileDialog.ShowDialog() == true)
    {
    try
    {
    using (FileStream fs = new FileStream(saveFileDialog.FileName, FileMode.Create))
    {
    inkCanvas.Strokes.Save(fs);
    }
    currentFileName = saveFileDialog.FileName;
    UpdateStatus($"墨迹已保存到: {Path.GetFileName(currentFileName)}");
    }
    catch (Exception ex)
    {
    MessageBox.Show($"保存文件时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
    }
    }
    }

    private void OnLoadButtonClicked(object sender, RoutedEventArgs e)
    {
    Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog
    {
    Filter = "墨迹文件 (*.isf)|*.isf|所有文件 (*.*)|*.*",
    DefaultExt = ".isf"
    };

    if (openFileDialog.ShowDialog() == true)
    {
    try
    {
    using (FileStream fs = new FileStream(openFileDialog.FileName, FileMode.Open, FileAccess.Read))
    {
    StrokeCollection strokes = new StrokeCollection(fs);
    inkCanvas.Strokes = strokes;
    }
    currentFileName = openFileDialog.FileName;
    UpdateStatus($"已加载墨迹文件: {Path.GetFileName(currentFileName)}");
    }
    catch (Exception ex)
    {
    MessageBox.Show($"加载文件时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
    }
    }
    }

    private void OnUndoButtonClicked(object sender, RoutedEventArgs e)
    {
    if (inkCanvas.Strokes.Count > 0)
    {
    inkCanvas.Strokes.RemoveAt(inkCanvas.Strokes.Count 1);
    UpdateStatus("撤销上一个笔画");
    }
    else
    {
    UpdateStatus("没有可撤销的笔画");
    }
    }

    private void OnRedoButtonClicked(object sender, RoutedEventArgs e)
    {
    // 在实际应用中,需要维护一个重做栈
    // 这里简化为提示信息
    UpdateStatus("重做功能需要实现历史记录管理");
    }

    private void OnInkCanvasGesture(object sender, InkCanvasGestureEventArgs e)
    {
    ReadOnlyCollection<GestureRecognitionResult> gestureResults = e.GetGestureRecognitionResults();

    if (gestureResults.Count > 0)
    {
    GestureRecognitionResult bestResult = gestureResults[0];

    if (bestResult.RecognitionConfidence == RecognitionConfidence.Strong ||
    bestResult.RecognitionConfidence == RecognitionConfidence.Intermediate)
    {
    ApplicationGesture recognizedGesture = bestResult.ApplicationGesture;

    switch (recognizedGesture)
    {
    case ApplicationGesture.Circle:
    UpdateStatus("识别到: 圆形");
    break;
    case ApplicationGesture.Square:
    UpdateStatus("识别到: 方形");
    break;
    case ApplicationGesture.Triangle:
    UpdateStatus("识别到: 三角形");
    break;
    case ApplicationGesture.Check:
    UpdateStatus("识别到: 勾选标记");
    break;
    case ApplicationGesture.ScratchOut:
    // 涂鸦删除手势
    inkCanvas.Strokes.Remove(e.Strokes[0]);
    UpdateStatus("识别到删除手势,已删除笔画");
    break;
    default:
    UpdateStatus($"识别到手势: {recognizedGesture}");
    break;
    }
    }
    }
    }

    private void OnStrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
    {
    Stroke newStroke = e.Stroke;
    UpdateStatus($"添加了新笔画,ID: {newStroke.GetHashCode()}");
    }

    private void OnStrokeErased(object sender, RoutedEventArgs e)
    {
    UpdateStatus("笔画已被擦除");
    }

    private void OnStrokeErasing(object sender, InkCanvasStrokeErasingEventArgs e)
    {
    // 可以在这里添加确认逻辑
    // e.Cancel = true; // 取消擦除操作
    }

    private void UpdateStatus(string message)
    {
    if (((Grid)this.Content).Children[2] is TextBlock statusBar)
    {
    statusBar.Text = $"{DateTime.Now:HH:mm:ss}{message}";
    }
    }

    // 网络白板特定功能
    public void ReceiveRemoteStroke(Stroke stroke)
    {
    // 从网络接收笔画并添加到画板
    Dispatcher.Invoke(() =>
    {
    inkCanvas.Strokes.Add(stroke);
    UpdateStatus("收到远程笔画");
    });
    }

    public StrokeCollection GetAllStrokes()
    {
    // 获取所有笔画用于网络传输
    return inkCanvas.Strokes.Clone();
    }

    public void ClearAllStrokes()
    {
    Dispatcher.Invoke(() =>
    {
    inkCanvas.Strokes.Clear();
    UpdateStatus("清空所有笔画");
    });
    }
    }

    // 网络白板客户端模拟
    public class NetworkWhiteboardClient
    {
    private BasicWhiteboardWindow whiteboardWindow;
    private Random random = new Random();

    public NetworkWhiteboardClient()
    {
    whiteboardWindow = new BasicWhiteboardWindow();
    whiteboardWindow.Title = "网络白板客户端";
    whiteboardWindow.Width = 800;
    whiteboardWindow.Height = 600;
    }

    public void Show()
    {
    whiteboardWindow.Show();
    }

    // 模拟接收网络数据
    public void SimulateNetworkReceive()
    {
    // 模拟创建随机笔画
    StylusPointCollection points = new StylusPointCollection();

    int startX = random.Next(100, 500);
    int startY = random.Next(100, 400);

    for (int i = 0; i < 10; i++)
    {
    points.Add(new StylusPoint(startX + i * 10, startY + random.Next(10, 10)));
    }

    DrawingAttributes attributes = new DrawingAttributes
    {
    Color = Color.FromRgb(
    (byte)random.Next(50, 255),
    (byte)random.Next(50, 255),
    (byte)random.Next(50, 255)),
    Width = random.Next(2, 10),
    Height = random.Next(2, 10),
    FitToCurve = true
    };

    Stroke remoteStroke = new Stroke(points, attributes);
    whiteboardWindow.ReceiveRemoteStroke(remoteStroke);
    }

    // 模拟发送数据
    public StrokeCollection GetStrokesForSending()
    {
    return whiteboardWindow.GetAllStrokes();
    }
    }

    // 应用程序入口
    public class WhiteboardApp
    {
    [STAThread]
    public static void Main()
    {
    Application app = new Application();

    // 创建客户端实例
    NetworkWhiteboardClient client = new NetworkWhiteboardClient();

    // 创建控制面板
    Window controlPanel = new Window
    {
    Title = "白板控制面板",
    Width = 300,
    Height = 200,
    WindowStartupLocation = WindowStartupLocation.CenterScreen
    };

    StackPanel panel = new StackPanel
    {
    Margin = new Thickness(20)
    };

    Button showWhiteboardButton = new Button
    {
    Content = "显示白板",
    Margin = new Thickness(0, 0, 0, 10),
    Padding = new Thickness(20, 10, 20, 10)
    };
    showWhiteboardButton.Click += (s, e) => client.Show();

    Button simulateReceiveButton = new Button
    {
    Content = "模拟接收网络笔画",
    Margin = new Thickness(0, 0, 0, 10),
    Padding = new Thickness(20, 10, 20, 10)
    };
    simulateReceiveButton.Click += (s, e) => client.SimulateNetworkReceive();

    Button exitButton = new Button
    {
    Content = "退出",
    Padding = new Thickness(20, 10, 20, 10)
    };
    exitButton.Click += (s, e) => app.Shutdown();

    panel.Children.Add(showWhiteboardButton);
    panel.Children.Add(simulateReceiveButton);
    panel.Children.Add(exitButton);

    controlPanel.Content = panel;

    app.Run(controlPanel);
    }
    }
    }

    3.2.2 高级触笔事件处理与手势识别

    在专业的网络绘图应用中,触笔事件和手势识别提供了更加自然的用户交互体验。WPF提供了完整的触笔事件处理机制,允许开发者创建响应灵敏的绘图应用。

    触笔事件层次结构:

  • 原始触笔事件:StylusDown, StylusMove, StylusUp
  • 鼠标事件兼容:MouseDown, MouseMove, MouseUp
  • 手势事件:Gesture, StylusSystemGesture
  • 高级触笔事件处理示例:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Shapes;

    namespace AdvancedStylusHandling
    {
    public class AdvancedInkCanvas : Canvas
    {
    // 笔画集合
    private StrokeCollection strokes = new StrokeCollection();
    private Stroke currentStroke;
    private StylusPointCollection currentPoints;

    // 绘图属性
    private DrawingAttributes drawingAttributes = new DrawingAttributes
    {
    Color = Colors.Black,
    Width = 3,
    Height = 3,
    FitToCurve = true,
    IsHighlighter = false,
    IgnorePressure = false,
    StylusTip = StylusTip.Ellipse
    };

    // 手势识别
    private List<GestureRecognitionResult> lastGestureResults = new List<GestureRecognitionResult>();

    // 压力敏感支持
    private bool isPressureSensitive = true;

    // 事件
    public event EventHandler<StrokeEventArgs> StrokeCollected;
    public event EventHandler<GestureRecognizedEventArgs> GestureRecognized;
    public event EventHandler<StylusPressureEventArgs> StylusPressureChanged;

    public AdvancedInkCanvas()
    {
    this.Background = Brushes.White;

    // 启用触笔事件
    this.Focusable = true;
    this.FocusVisualStyle = null;

    // 设置触笔事件处理
    this.StylusDown += OnStylusDown;
    this.StylusMove += OnStylusMove;
    this.StylusUp += OnStylusUp;
    this.StylusSystemGesture += OnStylusSystemGesture;

    // 鼠标事件作为备用
    this.MouseDown += OnMouseDown;
    this.MouseMove += OnMouseMove;
    this.MouseUp += OnMouseUp;

    // 启用触笔设备检测
    Stylus.IsPressAndHoldEnabled = false;
    Stylus.IsFlicksEnabled = false;
    Stylus.IsTapFeedbackEnabled = true;
    Stylus.IsTouchFeedbackEnabled = true;
    }

    // 绘图属性
    public DrawingAttributes DrawingAttributes
    {
    get => drawingAttributes;
    set => drawingAttributes = value?.Clone() ?? new DrawingAttributes();
    }

    public StrokeCollection Strokes
    {
    get => strokes.Clone();
    set
    {
    strokes = value?.Clone() ?? new StrokeCollection();
    InvalidateVisual();
    }
    }

    public bool IsPressureSensitive
    {
    get => isPressureSensitive;
    set => isPressureSensitive = value;
    }

    // 触笔事件处理
    private void OnStylusDown(object sender, StylusDownEventArgs e)
    {
    Debug.WriteLine($"触笔按下: ID={e.StylusDevice.Id}, 位置={e.GetPosition(this)}");

    // 捕获触笔设备
    Stylus.Capture(this);

    // 开始新的笔画
    currentPoints = new StylusPointCollection();

    // 获取第一个点
    StylusPoint firstPoint = e.GetStylusPoints(this)[0];
    if (isPressureSensitive && firstPoint.PressureFactor > 0)
    {
    // 调整笔画粗细基于压力
    double pressureFactor = firstPoint.PressureFactor;
    double baseWidth = drawingAttributes.Width;
    double adjustedWidth = baseWidth * (0.5 + pressureFactor * 0.5);

    var adjustedAttributes = drawingAttributes.Clone();
    adjustedAttributes.Width = adjustedWidth;
    adjustedAttributes.Height = adjustedWidth;

    currentStroke = new Stroke(currentPoints, adjustedAttributes);
    }
    else
    {
    currentStroke = new Stroke(currentPoints, drawingAttributes.Clone());
    }

    // 添加第一个点
    currentPoints.Add(firstPoint);

    // 触发压力变化事件
    StylusPressureChanged?.Invoke(this, new StylusPressureEventArgs
    {
    Pressure = firstPoint.PressureFactor,
    Position = e.GetPosition(this)
    });

    e.Handled = true;
    }

    private void OnStylusMove(object sender, StylusEventArgs e)
    {
    if (currentStroke == null || currentPoints == null)
    return;

    // 获取当前触笔点
    StylusPointCollection newPoints = e.GetStylusPoints(this);

    foreach (StylusPoint point in newPoints)
    {
    // 添加点到当前笔画
    currentPoints.Add(point);

    // 更新压力敏感笔画
    if (isPressureSensitive && point.PressureFactor > 0)
    {
    double pressureFactor = point.PressureFactor;
    double baseWidth = drawingAttributes.Width;
    double adjustedWidth = baseWidth * (0.5 + pressureFactor * 0.5);

    // 动态调整笔画粗细
    if (Math.Abs(currentStroke.DrawingAttributes.Width adjustedWidth) > 0.1)
    {
    var adjustedAttributes = drawingAttributes.Clone();
    adjustedAttributes.Width = adjustedWidth;
    adjustedAttributes.Height = adjustedWidth;
    currentStroke.DrawingAttributes = adjustedAttributes;
    }

    // 触发压力变化事件
    StylusPressureChanged?.Invoke(this, new StylusPressureEventArgs
    {
    Pressure = point.PressureFactor,
    Position = e.GetPosition(this)
    });
    }
    }

    // 重绘
    InvalidateVisual();

    e.Handled = true;
    }

    private void OnStylusUp(object sender, StylusEventArgs e)
    {
    Debug.WriteLine($"触笔抬起: ID={e.StylusDevice.Id}");

    if (currentStroke == null)
    return;

    // 添加最后一个点
    StylusPointCollection finalPoints = e.GetStylusPoints(this);
    if (finalPoints.Count > 0)
    {
    currentPoints.Add(finalPoints[0]);
    }

    // 完成笔画
    if (currentPoints.Count > 1)
    {
    // 检查是否为手势
    CheckForGesture(currentStroke);

    // 添加到笔画集合
    strokes.Add(currentStroke);

    // 触发事件
    StrokeCollected?.Invoke(this, new StrokeEventArgs(currentStroke));
    }

    // 清理
    currentStroke = null;
    currentPoints = null;

    // 释放触笔捕获
    Stylus.Capture(null);

    // 重绘
    InvalidateVisual();

    e.Handled = true;
    }

    private void OnStylusSystemGesture(object sender, StylusSystemGestureEventArgs e)
    {
    Debug.WriteLine($"系统手势: {e.SystemGesture}");

    switch (e.SystemGesture)
    {
    case SystemGesture.Tap:
    HandleTapGesture(e.GetPosition(this));
    break;
    case SystemGesture.DoubleTap:
    HandleDoubleTapGesture(e.GetPosition(this));
    break;
    case SystemGesture.RightTap:
    HandleRightTapGesture(e.GetPosition(this));
    break;
    case SystemGesture.Drag:
    HandleDragGesture(e.GetPosition(this));
    break;
    case SystemGesture.HoldEnter:
    HandleHoldEnterGesture(e.GetPosition(this));
    break;
    case SystemGesture.HoldLeave:
    HandleHoldLeaveGesture(e.GetPosition(this));
    break;
    }

    e.Handled = true;
    }

    // 鼠标事件处理(兼容性)
    private void OnMouseDown(object sender, MouseButtonEventArgs e)
    {
    if (e.StylusDevice != null && e.StylusDevice.IsValid)
    return; // 触笔设备已处理

    Debug.WriteLine($"鼠标按下: 按钮={e.ChangedButton}, 位置={e.GetPosition(this)}");

    // 模拟触笔按下
    StylusPointCollection points = new StylusPointCollection();
    points.Add(new StylusPoint(e.GetPosition(this).X, e.GetPosition(this).Y, 0.5f));

    currentPoints = new StylusPointCollection();
    currentStroke = new Stroke(currentPoints, drawingAttributes.Clone());
    currentPoints.Add(points[0]);

    Mouse.Capture(this);
    e.Handled = true;
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
    if (currentStroke == null || currentPoints == null)
    return;

    if (e.StylusDevice != null && e.StylusDevice.IsValid)
    return; // 触笔设备已处理

    Point currentPosition = e.GetPosition(this);
    currentPoints.Add(new StylusPoint(currentPosition.X, currentPosition.Y, 0.5f));

    InvalidateVisual();
    e.Handled = true;
    }

    private void OnMouseUp(object sender, MouseButtonEventArgs e)
    {
    if (currentStroke == null)
    return;

    if (e.StylusDevice != null && e.StylusDevice.IsValid)
    return; // 触笔设备已处理

    Point finalPosition = e.GetPosition(this);
    currentPoints.Add(new StylusPoint(finalPosition.X, finalPosition.Y, 0.5f));

    if (currentPoints.Count > 1)
    {
    strokes.Add(currentStroke);
    StrokeCollected?.Invoke(this, new StrokeEventArgs(currentStroke));
    }

    currentStroke = null;
    currentPoints = null;

    Mouse.Capture(null);
    InvalidateVisual();
    e.Handled = true;
    }

    // 手势识别
    private void CheckForGesture(Stroke stroke)
    {
    // 准备手势识别器
    ApplicationGesture[] enabledGestures = new[]
    {
    ApplicationGesture.Circle,
    ApplicationGesture.Square,
    ApplicationGesture.Triangle,
    ApplicationGesture.Check,
    ApplicationGesture.ChevronUp,
    ApplicationGesture.ChevronDown,
    ApplicationGesture.ChevronLeft,
    ApplicationGesture.ChevronRight,
    ApplicationGesture.Curlicue,
    ApplicationGesture.DoubleCurlicue,
    ApplicationGesture.Exclamation,
    ApplicationGesture.Star
    };

    // 创建手势识别器
    StrokeCollection gestureStrokes = new StrokeCollection { stroke };
    var recognizer = new GestureRecognizer(enabledGestures);

    // 识别手势
    lastGestureResults.Clear();
    lastGestureResults.AddRange(recognizer.Recognize(gestureStrokes));

    if (lastGestureResults.Count > 0)
    {
    GestureRecognitionResult bestResult = lastGestureResults[0];

    if (bestResult.RecognitionConfidence == RecognitionConfidence.Strong ||
    bestResult.RecognitionConfidence == RecognitionConfidence.Intermediate)
    {
    // 触发手势识别事件
    GestureRecognized?.Invoke(this, new GestureRecognizedEventArgs
    {
    Gesture = bestResult.ApplicationGesture,
    Confidence = bestResult.RecognitionConfidence,
    Stroke = stroke
    });

    Debug.WriteLine($"识别到手势: {bestResult.ApplicationGesture} (置信度: {bestResult.RecognitionConfidence})");
    }
    }
    }

    // 手势处理
    private void HandleTapGesture(Point position)
    {
    Debug.WriteLine($"点击手势在位置: {position}");

    // 查找附近的笔画
    Stroke nearestStroke = FindNearestStroke(position, 20);
    if (nearestStroke != null)
    {
    // 高亮选中的笔画
    ToggleStrokeSelection(nearestStroke);
    }
    }

    private void HandleDoubleTapGesture(Point position)
    {
    Debug.WriteLine($"双击手势在位置: {position}");

    // 清除所有笔画
    strokes.Clear();
    InvalidateVisual();
    }

    private void HandleRightTapGesture(Point position)
    {
    Debug.WriteLine($"右击手势在位置: {position}");

    // 显示上下文菜单
    ShowContextMenu(position);
    }

    private void HandleDragGesture(Point position)
    {
    Debug.WriteLine($"拖拽手势在位置: {position}");
    }

    private void HandleHoldEnterGesture(Point position)
    {
    Debug.WriteLine($"长按开始: {position}");

    // 显示长按反馈
    ShowHoldFeedback(position);
    }

    private void HandleHoldLeaveGesture(Point position)
    {
    Debug.WriteLine($"长按结束: {position}");

    // 隐藏长按反馈
    HideHoldFeedback();
    }

    // 辅助方法
    private Stroke FindNearestStroke(Point point, double tolerance)
    {
    Stroke nearestStroke = null;
    double minDistance = double.MaxValue;

    foreach (Stroke stroke in strokes)
    {
    Rect bounds = stroke.GetBounds();
    bounds.Inflate(tolerance, tolerance);

    if (bounds.Contains(point))
    {
    // 计算点到笔画的实际距离
    double distance = CalculateDistanceToStroke(point, stroke);
    if (distance < minDistance && distance <= tolerance)
    {
    minDistance = distance;
    nearestStroke = stroke;
    }
    }
    }

    return nearestStroke;
    }

    private double CalculateDistanceToStroke(Point point, Stroke stroke)
    {
    double minDistance = double.MaxValue;

    for (int i = 0; i < stroke.StylusPoints.Count 1; i++)
    {
    StylusPoint p1 = stroke.StylusPoints[i];
    StylusPoint p2 = stroke.StylusPoints[i + 1];

    double distance = DistanceFromPointToLineSegment(
    point,
    new Point(p1.X, p1.Y),
    new Point(p2.X, p2.Y));

    minDistance = Math.Min(minDistance, distance);
    }

    return minDistance;
    }

    private double DistanceFromPointToLineSegment(Point point, Point lineStart, Point lineEnd)
    {
    double lineLengthSquared = (lineEnd.X lineStart.X) * (lineEnd.X lineStart.X) +
    (lineEnd.Y lineStart.Y) * (lineEnd.Y lineStart.Y);

    if (lineLengthSquared == 0)
    return Distance(point, lineStart);

    double t = Math.Max(0, Math.Min(1,
    ((point.X lineStart.X) * (lineEnd.X lineStart.X) +
    (point.Y lineStart.Y) * (lineEnd.Y lineStart.Y)) / lineLengthSquared));

    Point projection = new Point(
    lineStart.X + t * (lineEnd.X lineStart.X),
    lineStart.Y + t * (lineEnd.Y lineStart.Y));

    return Distance(point, projection);
    }

    private double Distance(Point p1, Point p2)
    {
    double dx = p2.X p1.X;
    double dy = p2.Y p1.Y;
    return Math.Sqrt(dx * dx + dy * dy);
    }

    private void ToggleStrokeSelection(Stroke stroke)
    {
    // 切换笔画的选择状态
    if (stroke.DrawingAttributes.Color == Colors.Red)
    {
    // 取消选择
    var attributes = drawingAttributes.Clone();
    stroke.DrawingAttributes = attributes;
    }
    else
    {
    // 选择
    var selectedAttributes = drawingAttributes.Clone();
    selectedAttributes.Color = Colors.Red;
    stroke.DrawingAttributes = selectedAttributes;
    }

    InvalidateVisual();
    }

    private void ShowContextMenu(Point position)
    {
    ContextMenu contextMenu = new ContextMenu();

    MenuItem deleteItem = new MenuItem { Header = "删除笔画" };
    deleteItem.Click += (s, e) =>
    {
    Stroke nearestStroke = FindNearestStroke(position, 20);
    if (nearestStroke != null)
    {
    strokes.Remove(nearestStroke);
    InvalidateVisual();
    }
    };

    MenuItem changeColorItem = new MenuItem { Header = "更改颜色" };
    changeColorItem.Click += (s, e) =>
    {
    Stroke nearestStroke = FindNearestStroke(position, 20);
    if (nearestStroke != null)
    {
    var random = new Random();
    var newAttributes = nearestStroke.DrawingAttributes.Clone();
    newAttributes.Color = Color.FromRgb(
    (byte)random.Next(50, 255),
    (byte)random.Next(50, 255),
    (byte)random.Next(50, 255));
    nearestStroke.DrawingAttributes = newAttributes;
    InvalidateVisual();
    }
    };

    contextMenu.Items.Add(deleteItem);
    contextMenu.Items.Add(changeColorItem);
    contextMenu.IsOpen = true;
    }

    private void ShowHoldFeedback(Point position)
    {
    // 创建长按反馈视觉效果
    Ellipse feedbackCircle = new Ellipse
    {
    Width = 40,
    Height = 40,
    Fill = new RadialGradientBrush(Colors.White, Colors.LightBlue)
    {
    RadiusX = 0.5,
    RadiusY = 0.5,
    Center = new Point(0.5, 0.5),
    GradientOrigin = new Point(0.5, 0.5)
    },
    Opacity = 0.7,
    RenderTransform = new TranslateTransform(position.X 20, position.Y 20)
    };

    feedbackCircle.SetValue(ZIndexProperty, 1000);
    this.Children.Add(feedbackCircle);
    }

    private void HideHoldFeedback()
    {
    // 移除所有反馈元素
    for (int i = this.Children.Count 1; i >= 0; i)
    {
    if (this.Children[i] is Ellipse ellipse && ellipse.Width == 40 && ellipse.Height == 40)
    {
    this.Children.RemoveAt(i);
    }
    }
    }

    // 渲染
    protected override void OnRender(DrawingContext drawingContext)
    {
    base.OnRender(drawingContext);

    // 绘制所有笔画
    foreach (Stroke stroke in strokes)
    {
    stroke.Draw(drawingContext);
    }

    // 绘制当前正在绘制的笔画
    if (currentStroke != null)
    {
    currentStroke.Draw(drawingContext);
    }

    // 绘制手势识别结果
    if (lastGestureResults.Count > 0)
    {
    GestureRecognitionResult bestResult = lastGestureResults[0];
    if (bestResult.RecognitionConfidence != RecognitionConfidence.Poor)
    {
    DrawGestureFeedback(drawingContext, bestResult);
    }
    }
    }

    private void DrawGestureFeedback(DrawingContext drawingContext, GestureRecognitionResult result)
    {
    if (currentStroke == null)
    return;

    Rect bounds = currentStroke.GetBounds();
    Point center = new Point(bounds.X + bounds.Width / 2, bounds.Y + bounds.Height / 2);

    string gestureText = result.ApplicationGesture.ToString();
    if (result.RecognitionConfidence == RecognitionConfidence.Intermediate)
    {
    gestureText += "?";
    }

    FormattedText formattedText = new FormattedText(
    gestureText,
    System.Globalization.CultureInfo.CurrentCulture,
    FlowDirection.LeftToRight,
    new Typeface("Arial"),
    12,
    Brushes.Blue,
    VisualTreeHelper.GetDpi(this).PixelsPerDip);

    drawingContext.DrawText(formattedText,
    new Point(center.X formattedText.Width / 2, center.Y formattedText.Height / 2));
    }
    }

    // 自定义事件参数类
    public class StrokeEventArgs : EventArgs
    {
    public Stroke Stroke { get; set; }

    public StrokeEventArgs(Stroke stroke)
    {
    Stroke = stroke;
    }
    }

    public class GestureRecognizedEventArgs : EventArgs
    {
    public ApplicationGesture Gesture { get; set; }
    public RecognitionConfidence Confidence { get; set; }
    public Stroke Stroke { get; set; }
    }

    public class StylusPressureEventArgs : EventArgs
    {
    public float Pressure { get; set; }
    public Point Position { get; set; }
    }

    // 测试应用程序
    public class AdvancedStylusApp
    {
    [STAThread]
    public static void Main()
    {
    Application app = new Application();

    Window mainWindow = new Window
    {
    Title = "高级触笔事件处理演示",
    Width = 800,
    Height = 600,
    WindowStartupLocation = WindowStartupLocation.CenterScreen
    };

    Grid mainGrid = new Grid();

    // 创建高级墨迹画板
    AdvancedInkCanvas inkCanvas = new AdvancedInkCanvas();

    // 设置事件处理
    inkCanvas.StrokeCollected += (s, e) =>
    {
    Debug.WriteLine($"笔画收集: 点数={e.Stroke.StylusPoints.Count}");
    };

    inkCanvas.GestureRecognized += (s, e) =>
    {
    MessageBox.Show($"识别到手势: {e.Gesture} (置信度: {e.Confidence})",
    "手势识别", MessageBoxButton.OK, MessageBoxImage.Information);
    };

    inkCanvas.StylusPressureChanged += (s, e) =>
    {
    // 可以在状态栏显示压力值
    mainWindow.Title = $"压力: {e.Pressure:F2} – 高级触笔事件处理演示";
    };

    // 创建控制面板
    StackPanel controlPanel = new StackPanel
    {
    Orientation = Orientation.Horizontal,
    Background = Brushes.LightGray,
    Height = 40,
    VerticalAlignment = VerticalAlignment.Top
    };

    // 压力敏感开关
    CheckBox pressureCheckBox = new CheckBox
    {
    Content = "压力敏感",
    IsChecked = inkCanvas.IsPressureSensitive,
    Margin = new Thickness(10, 10, 10, 10),
    VerticalAlignment = VerticalAlignment.Center
    };
    pressureCheckBox.Checked += (s, e) => inkCanvas.IsPressureSensitive = true;
    pressureCheckBox.Unchecked += (s, e) => inkCanvas.IsPressureSensitive = false;

    // 颜色选择
    ComboBox colorComboBox = new ComboBox
    {
    Width = 100,
    Margin = new Thickness(10, 10, 10, 10),
    ItemsSource = new[]
    {
    Colors.Black,
    Colors.Red,
    Colors.Blue,
    Colors.Green,
    Colors.Purple,
    Colors.Orange
    },
    SelectedIndex = 0
    };
    colorComboBox.SelectionChanged += (s, e) =>
    {
    if (colorComboBox.SelectedItem is Color selectedColor)
    {
    inkCanvas.DrawingAttributes.Color = selectedColor;
    }
    };

    // 线宽调整
    Slider widthSlider = new Slider
    {
    Width = 100,
    Minimum = 1,
    Maximum = 20,
    Value = 3,
    Margin = new Thickness(10, 10, 10, 10)
    };
    widthSlider.ValueChanged += (s, e) =>
    {
    inkCanvas.DrawingAttributes.Width = widthSlider.Value;
    inkCanvas.DrawingAttributes.Height = widthSlider.Value;
    };

    // 清除按钮
    Button clearButton = new Button
    {
    Content = "清除",
    Margin = new Thickness(10, 10, 10, 10),
    Padding = new Thickness(20, 5, 20, 5)
    };
    clearButton.Click += (s, e) => inkCanvas.Strokes = new StrokeCollection();

    controlPanel.Children.Add(pressureCheckBox);
    controlPanel.Children.Add(new Label { Content = "颜色:", VerticalAlignment = VerticalAlignment.Center });
    controlPanel.Children.Add(colorComboBox);
    controlPanel.Children.Add(new Label { Content = "线宽:", VerticalAlignment = VerticalAlignment.Center });
    controlPanel.Children.Add(widthSlider);
    controlPanel.Children.Add(clearButton);

    // 添加到网格
    mainGrid.Children.Add(inkCanvas);
    mainGrid.Children.Add(controlPanel);

    mainWindow.Content = mainGrid;

    app.Run(mainWindow);
    }
    }
    }

    3.2.3 墨迹数据结构与网络传输优化

    在网络协作应用中,墨迹数据的高效传输至关重要。Stroke和StrokeCollection类提供了墨迹数据的序列化和反序列化功能,但直接传输原始数据可能效率低下。

    墨迹数据优化策略:

  • 数据压缩:使用差分编码和行程编码
  • 增量传输:只传输变化的笔画
  • 带宽适应:根据网络状况调整数据精度
  • 批处理:合并多个笔画一次性传输
  • 优化的网络墨迹传输系统:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.IO.Compression;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Threading;

    namespace OptimizedInkNetworkTransfer
    {
    // 优化的墨迹数据结构
    public class OptimizedStrokeData
    {
    public Guid StrokeId { get; set; }
    public Guid SessionId { get; set; }
    public Guid UserId { get; set; }
    public byte[] CompressedPoints { get; set; }
    public byte[] DrawingAttributesData { get; set; }
    public DateTime Timestamp { get; set; }
    public bool IsDeleted { get; set; }

    // 用于增量更新
    public long Version { get; set; }

    public OptimizedStrokeData()
    {
    StrokeId = Guid.NewGuid();
    Timestamp = DateTime.UtcNow;
    Version = 1;
    }

    public static OptimizedStrokeData FromStroke(Stroke stroke, Guid sessionId, Guid userId)
    {
    var data = new OptimizedStrokeData
    {
    SessionId = sessionId,
    UserId = userId
    };

    // 压缩点数据
    data.CompressedPoints = CompressPoints(stroke.StylusPoints);

    // 序列化绘图属性
    data.DrawingAttributesData = SerializeDrawingAttributes(stroke.DrawingAttributes);

    return data;
    }

    public Stroke ToStroke()
    {
    if (IsDeleted)
    return null;

    StylusPointCollection points = DecompressPoints(CompressedPoints);
    DrawingAttributes attributes = DeserializeDrawingAttributes(DrawingAttributesData);

    return new Stroke(points, attributes);
    }

    private static byte[] CompressPoints(StylusPointCollection points)
    {
    if (points == null || points.Count == 0)
    return new byte[0];

    using (MemoryStream ms = new MemoryStream())
    {
    using (BinaryWriter writer = new BinaryWriter(ms))
    {
    // 写入点数
    writer.Write(points.Count);

    // 使用差分编码和行程编码
    StylusPoint? previousPoint = null;
    int runLength = 1;

    for (int i = 0; i < points.Count; i++)
    {
    StylusPoint currentPoint = points[i];

    if (previousPoint.HasValue)
    {
    // 计算差分
    float deltaX = currentPoint.X previousPoint.Value.X;
    float deltaY = currentPoint.Y previousPoint.Value.Y;
    float deltaPressure = currentPoint.PressureFactor previousPoint.Value.PressureFactor;

    // 检查是否连续点(用于行程编码)
    if (Math.Abs(deltaX) < 0.1f && Math.Abs(deltaY) < 0.1f && Math.Abs(deltaPressure) < 0.01f)
    {
    runLength++;
    }
    else
    {
    // 写入行程长度
    if (runLength > 1)
    {
    writer.Write((byte)0xFF); // 行程编码标记
    writer.Write(runLength);
    }

    // 写入差分数据
    WriteCompressedFloat(writer, deltaX);
    WriteCompressedFloat(writer, deltaY);
    WriteCompressedFloat(writer, deltaPressure);

    runLength = 1;
    }
    }
    else
    {
    // 第一个点:写入绝对坐标
    writer.Write(currentPoint.X);
    writer.Write(currentPoint.Y);
    writer.Write(currentPoint.PressureFactor);
    }

    previousPoint = currentPoint;
    }

    // 写入最后一个行程
    if (runLength > 1)
    {
    writer.Write((byte)0xFF);
    writer.Write(runLength);
    }
    }

    // 使用GZip压缩
    return CompressData(ms.ToArray());
    }
    }

    private static StylusPointCollection DecompressPoints(byte[] compressedData)
    {
    if (compressedData == null || compressedData.Length == 0)
    return new StylusPointCollection();

    // 解压缩
    byte[] decompressed = DecompressData(compressedData);

    using (MemoryStream ms = new MemoryStream(decompressed))
    using (BinaryReader reader = new BinaryReader(ms))
    {
    int pointCount = reader.ReadInt32();
    StylusPointCollection points = new StylusPointCollection();

    if (pointCount == 0)
    return points;

    // 读取第一个点(绝对坐标)
    float x = reader.ReadSingle();
    float y = reader.ReadSingle();
    float pressure = reader.ReadSingle();

    points.Add(new StylusPoint(x, y, pressure));

    // 读取后续点(差分编码)
    for (int i = 1; i < pointCount; i++)
    {
    byte marker = reader.ReadByte();

    if (marker == 0xFF)
    {
    // 行程编码
    int runLength = reader.ReadInt32();
    StylusPoint lastPoint = points[points.Count 1];

    for (int j = 0; j < runLength; j++)
    {
    points.Add(new StylusPoint(lastPoint.X, lastPoint.Y, lastPoint.PressureFactor));
    i++;
    }
    i; // 调整循环计数器
    }
    else
    {
    // 回退读取标记字节
    ms.Seek(1, SeekOrigin.Current);

    // 读取差分
    float deltaX = ReadCompressedFloat(reader);
    float deltaY = ReadCompressedFloat(reader);
    float deltaPressure = ReadCompressedFloat(reader);

    StylusPoint lastPoint = points[points.Count 1];
    points.Add(new StylusPoint(
    lastPoint.X + deltaX,
    lastPoint.Y + deltaY,
    lastPoint.PressureFactor + deltaPressure));
    }
    }

    return points;
    }
    }

    private static byte[] SerializeDrawingAttributes(DrawingAttributes attributes)
    {
    using (MemoryStream ms = new MemoryStream())
    using (BinaryWriter writer = new BinaryWriter(ms))
    {
    writer.Write(attributes.Color.A);
    writer.Write(attributes.Color.R);
    writer.Write(attributes.Color.G);
    writer.Write(attributes.Color.B);
    writer.Write(attributes.Width);
    writer.Write(attributes.Height);
    writer.Write(attributes.FitToCurve);
    writer.Write(attributes.IsHighlighter);
    writer.Write(attributes.IgnorePressure);
    writer.Write((int)attributes.StylusTip);

    return ms.ToArray();
    }
    }

    private static DrawingAttributes DeserializeDrawingAttributes(byte[] data)
    {
    if (data == null || data.Length == 0)
    return new DrawingAttributes();

    using (MemoryStream ms = new MemoryStream(data))
    using (BinaryReader reader = new BinaryReader(ms))
    {
    byte a = reader.ReadByte();
    byte r = reader.ReadByte();
    byte g = reader.ReadByte();
    byte b = reader.ReadByte();

    var attributes = new DrawingAttributes
    {
    Color = Color.FromArgb(a, r, g, b),
    Width = reader.ReadDouble(),
    Height = reader.ReadDouble(),
    FitToCurve = reader.ReadBoolean(),
    IsHighlighter = reader.ReadBoolean(),
    IgnorePressure = reader.ReadBoolean(),
    StylusTip = (StylusTip)reader.ReadInt32()
    };

    return attributes;
    }
    }

    private static void WriteCompressedFloat(BinaryWriter writer, float value)
    {
    // 将浮点数转换为定点数进行压缩
    short compressed = (short)(value * 100); // 保留2位小数精度
    writer.Write(compressed);
    }

    private static float ReadCompressedFloat(BinaryReader reader)
    {
    short compressed = reader.ReadInt16();
    return compressed / 100.0f;
    }

    private static byte[] CompressData(byte[] data)
    {
    using (MemoryStream ms = new MemoryStream())
    {
    using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress))
    {
    gzip.Write(data, 0, data.Length);
    }
    return ms.ToArray();
    }
    }

    private static byte[] DecompressData(byte[] compressedData)
    {
    using (MemoryStream ms = new MemoryStream(compressedData))
    using (GZipStream gzip = new GZipStream(ms, CompressionMode.Decompress))
    using (MemoryStream output = new MemoryStream())
    {
    gzip.CopyTo(output);
    return output.ToArray();
    }
    }
    }

    // 网络墨迹传输管理器
    public class NetworkInkTransferManager
    {
    private readonly Dictionary<Guid, OptimizedStrokeData> strokeRegistry = new Dictionary<Guid, OptimizedStrokeData>();
    private readonly Dictionary<Guid, List<OptimizedStrokeData>> sessionStrokes = new Dictionary<Guid, List<OptimizedStrokeData>>();

    private readonly object syncLock = new object();
    private readonly int batchSize = 10;
    private readonly TimeSpan batchInterval = TimeSpan.FromMilliseconds(100);

    private DispatcherTimer batchTimer;
    private List<OptimizedStrokeData> pendingBatch = new List<OptimizedStrokeData>();

    public event EventHandler<StrokeBatchEventArgs> BatchReadyForTransfer;
    public event EventHandler<StrokeEventArgs> StrokeReceived;

    public NetworkInkTransferManager()
    {
    batchTimer = new DispatcherTimer
    {
    Interval = batchInterval
    };
    batchTimer.Tick += OnBatchTimerTick;
    }

    // 添加新笔画到批处理
    public void AddStroke(Stroke stroke, Guid sessionId, Guid userId)
    {
    var strokeData = OptimizedStrokeData.FromStroke(stroke, sessionId, userId);

    lock (syncLock)
    {
    // 注册笔画
    strokeRegistry[strokeData.StrokeId] = strokeData;

    // 添加到会话
    if (!sessionStrokes.ContainsKey(sessionId))
    {
    sessionStrokes[sessionId] = new List<OptimizedStrokeData>();
    }
    sessionStrokes[sessionId].Add(strokeData);

    // 添加到待处理批处理
    pendingBatch.Add(strokeData);

    // 如果批处理已满,立即发送
    if (pendingBatch.Count >= batchSize)
    {
    SendBatch();
    }
    else
    {
    // 启动或重置批处理定时器
    batchTimer.Stop();
    batchTimer.Start();
    }
    }
    }

    // 删除笔画
    public void DeleteStroke(Guid strokeId)
    {
    lock (syncLock)
    {
    if (strokeRegistry.TryGetValue(strokeId, out var strokeData))
    {
    strokeData.IsDeleted = true;
    strokeData.Version++;
    strokeData.Timestamp = DateTime.UtcNow;

    pendingBatch.Add(strokeData);

    if (pendingBatch.Count >= batchSize)
    {
    SendBatch();
    }
    else
    {
    batchTimer.Stop();
    batchTimer.Start();
    }
    }
    }
    }

    // 接收远程笔画
    public void ReceiveStrokeData(OptimizedStrokeData strokeData)
    {
    lock (syncLock)
    {
    // 检查是否为新笔画或更新
    if (strokeRegistry.TryGetValue(strokeData.StrokeId, out var existingData))
    {
    // 版本控制:只接受更新的版本
    if (strokeData.Version > existingData.Version)
    {
    strokeRegistry[strokeData.StrokeId] = strokeData;

    // 更新会话中的笔画
    if (sessionStrokes.TryGetValue(strokeData.SessionId, out var sessionList))
    {
    int index = sessionList.FindIndex(s => s.StrokeId == strokeData.StrokeId);
    if (index >= 0)
    {
    sessionList[index] = strokeData;
    }
    }

    // 触发接收事件
    StrokeReceived?.Invoke(this, new StrokeEventArgs(strokeData));
    }
    }
    else
    {
    // 新笔画
    strokeRegistry[strokeData.StrokeId] = strokeData;

    if (!sessionStrokes.ContainsKey(strokeData.SessionId))
    {
    sessionStrokes[strokeData.SessionId] = new List<OptimizedStrokeData>();
    }
    sessionStrokes[strokeData.SessionId].Add(strokeData);

    // 触发接收事件
    StrokeReceived?.Invoke(this, new StrokeEventArgs(strokeData));
    }
    }
    }

    // 接收批处理数据
    public void ReceiveBatch(List<OptimizedStrokeData> batch)
    {
    foreach (var strokeData in batch)
    {
    ReceiveStrokeData(strokeData);
    }
    }

    // 获取会话中的所有笔画
    public List<Stroke> GetSessionStrokes(Guid sessionId)
    {
    lock (syncLock)
    {
    if (sessionStrokes.TryGetValue(sessionId, out var strokeDataList))
    {
    return strokeDataList
    .Where(s => !s.IsDeleted)
    .Select(s => s.ToStroke())
    .Where(s => s != null)
    .ToList();
    }
    return new List<Stroke>();
    }
    }

    // 获取笔画数据的压缩包(用于离线同步)
    public byte[] GetCompressedSessionData(Guid sessionId)
    {
    lock (syncLock)
    {
    if (!sessionStrokes.TryGetValue(sessionId, out var strokeDataList))
    return new byte[0];

    using (MemoryStream ms = new MemoryStream())
    using (BinaryWriter writer = new BinaryWriter(ms))
    {
    writer.Write(strokeDataList.Count);

    foreach (var strokeData in strokeDataList)
    {
    writer.Write(strokeData.StrokeId.ToByteArray());
    writer.Write(strokeData.SessionId.ToByteArray());
    writer.Write(strokeData.UserId.ToByteArray());
    writer.Write(strokeData.Version);
    writer.Write(strokeData.Timestamp.ToBinary());
    writer.Write(strokeData.IsDeleted);

    writer.Write(strokeData.CompressedPoints.Length);
    writer.Write(strokeData.CompressedPoints);

    writer.Write(strokeData.DrawingAttributesData.Length);
    writer.Write(strokeData.DrawingAttributesData);
    }

    // 二次压缩
    return OptimizedStrokeData.CompressData(ms.ToArray());
    }
    }
    }

    // 从压缩包恢复数据
    public void RestoreFromCompressedData(byte[] compressedData)
    {
    byte[] data = OptimizedStrokeData.DecompressData(compressedData);

    using (MemoryStream ms = new MemoryStream(data))
    using (BinaryReader reader = new BinaryReader(ms))
    {
    int count = reader.ReadInt32();

    for (int i = 0; i < count; i++)
    {
    var strokeData = new OptimizedStrokeData
    {
    StrokeId = new Guid(reader.ReadBytes(16)),
    SessionId = new Guid(reader.ReadBytes(16)),
    UserId = new Guid(reader.ReadBytes(16)),
    Version = reader.ReadInt64(),
    Timestamp = DateTime.FromBinary(reader.ReadInt64()),
    IsDeleted = reader.ReadBoolean()
    };

    int pointsLength = reader.ReadInt32();
    strokeData.CompressedPoints = reader.ReadBytes(pointsLength);

    int attributesLength = reader.ReadInt32();
    strokeData.DrawingAttributesData = reader.ReadBytes(attributesLength);

    ReceiveStrokeData(strokeData);
    }
    }
    }

    // 批处理定时器回调
    private void OnBatchTimerTick(object sender, EventArgs e)
    {
    batchTimer.Stop();
    SendBatch();
    }

    // 发送批处理
    private void SendBatch()
    {
    if (pendingBatch.Count == 0)
    return;

    List<OptimizedStrokeData> batchToSend;

    lock (syncLock)
    {
    batchToSend = new List<OptimizedStrokeData>(pendingBatch);
    pendingBatch.Clear();
    }

    // 触发批处理就绪事件
    BatchReadyForTransfer?.Invoke(this, new StrokeBatchEventArgs(batchToSend));

    Debug.WriteLine($"发送批处理: {batchToSend.Count} 个笔画");
    }

    // 统计数据
    public TransferStatistics GetStatistics()
    {
    lock (syncLock)
    {
    return new TransferStatistics
    {
    TotalStrokes = strokeRegistry.Count,
    ActiveSessions = sessionStrokes.Count,
    PendingBatchSize = pendingBatch.Count,
    RegistrySize = strokeRegistry.Count
    };
    }
    }
    }

    // 事件参数类
    public class StrokeBatchEventArgs : EventArgs
    {
    public List<OptimizedStrokeData> Batch { get; }

    public StrokeBatchEventArgs(List<OptimizedStrokeData> batch)
    {
    Batch = batch;
    }
    }

    public class StrokeEventArgs : EventArgs
    {
    public OptimizedStrokeData StrokeData { get; }

    public StrokeEventArgs(OptimizedStrokeData strokeData)
    {
    StrokeData = strokeData;
    }
    }

    // 统计信息
    public class TransferStatistics
    {
    public int TotalStrokes { get; set; }
    public int ActiveSessions { get; set; }
    public int PendingBatchSize { get; set; }
    public int RegistrySize { get; set; }

    public override string ToString()
    {
    return $"笔画总数: {TotalStrokes}, 活动会话: {ActiveSessions}, 待处理批处理: {PendingBatchSize}";
    }
    }

    // 网络协作白板
    public class NetworkCollaborationWhiteboard : Window
    {
    private InkCanvas inkCanvas;
    private NetworkInkTransferManager transferManager;

    private Guid currentSessionId = Guid.NewGuid();
    private Guid currentUserId = Guid.NewGuid();

    private TextBlock statusText;
    private DispatcherTimer statsTimer;

    public NetworkCollaborationWhiteboard()
    {
    InitializeComponent();
    SetupWhiteboard();
    SetupNetworkManager();
    SetupUI();
    }

    private void SetupWhiteboard()
    {
    inkCanvas = new InkCanvas
    {
    Background = Brushes.White,
    DefaultDrawingAttributes = new DrawingAttributes
    {
    Color = Colors.Black,
    Width = 3,
    Height = 3,
    FitToCurve = true
    },
    EditingMode = InkCanvasEditingMode.Ink
    };

    inkCanvas.StrokeCollected += OnStrokeCollected;
    inkCanvas.StrokeErasing += OnStrokeErasing;

    this.Content = inkCanvas;
    }

    private void SetupNetworkManager()
    {
    transferManager = new NetworkInkTransferManager();
    transferManager.BatchReadyForTransfer += OnBatchReadyForTransfer;
    transferManager.StrokeReceived += OnStrokeReceived;

    // 模拟网络延迟
    SetupNetworkSimulation();
    }

    private void SetupUI()
    {
    // 添加状态栏
    statusText = new TextBlock
    {
    Text = $"会话: {currentSessionId.ToString().Substring(0, 8)} | 用户: {currentUserId.ToString().Substring(0, 8)}",
    Background = Brushes.LightGray,
    Padding = new Thickness(10),
    VerticalAlignment = VerticalAlignment.Bottom,
    HorizontalAlignment = HorizontalAlignment.Left
    };

    // 创建装饰层
    AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(inkCanvas);
    if (adornerLayer != null)
    {
    adornerLayer.Add(new StatusAdorner(inkCanvas, statusText));
    }

    // 统计定时器
    statsTimer = new DispatcherTimer
    {
    Interval = TimeSpan.FromSeconds(2)
    };
    statsTimer.Tick += OnStatsTimerTick;
    statsTimer.Start();
    }

    private void SetupNetworkSimulation()
    {
    // 模拟网络延迟和丢包
    Random random = new Random();

    transferManager.BatchReadyForTransfer += (s, e) =>
    {
    // 模拟网络延迟(50-200ms)
    int delay = random.Next(50, 200);

    DispatcherTimer delayTimer = new DispatcherTimer
    {
    Interval = TimeSpan.FromMilliseconds(delay)
    };

    delayTimer.Tick += (timerSender, timerArgs) =>
    {
    delayTimer.Stop();

    // 模拟丢包(5%概率)
    if (random.Next(100) >= 5)
    {
    // 模拟接收批处理
    transferManager.ReceiveBatch(e.Batch);
    }
    else
    {
    Debug.WriteLine("模拟网络丢包");
    }
    };

    delayTimer.Start();
    };
    }

    // 事件处理
    private void OnStrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
    {
    Stroke newStroke = e.Stroke;

    // 发送到网络管理器
    transferManager.AddStroke(newStroke, currentSessionId, currentUserId);

    UpdateStatus($"添加笔画: {newStroke.StylusPoints.Count} 个点");
    }

    private void OnStrokeErasing(object sender, InkCanvasStrokeErasingEventArgs e)
    {
    // 在实际应用中,需要找到对应的StrokeId
    // 这里简化为提示
    UpdateStatus("正在擦除笔画…");
    }

    private void OnBatchReadyForTransfer(object sender, StrokeBatchEventArgs e)
    {
    UpdateStatus($"准备发送批处理: {e.Batch.Count} 个笔画");
    }

    private void OnStrokeReceived(object sender, StrokeEventArgs e)
    {
    Dispatcher.Invoke(() =>
    {
    if (e.StrokeData.IsDeleted)
    {
    // 删除笔画
    // 在实际应用中需要找到并删除对应的笔画
    UpdateStatus($"收到删除笔画请求: {e.StrokeData.StrokeId}");
    }
    else
    {
    // 添加新笔画
    Stroke remoteStroke = e.StrokeData.ToStroke();
    if (remoteStroke != null)
    {
    // 检查是否已存在(避免重复添加)
    bool exists = inkCanvas.Strokes.Any(s =>
    Math.Abs(s.GetBounds().X remoteStroke.GetBounds().X) < 1 &&
    Math.Abs(s.GetBounds().Y remoteStroke.GetBounds().Y) < 1);

    if (!exists)
    {
    inkCanvas.Strokes.Add(remoteStroke);
    UpdateStatus($"收到远程笔画: {e.StrokeData.UserId.ToString().Substring(0, 8)}");
    }
    }
    }
    });
    }

    private void OnStatsTimerTick(object sender, EventArgs e)
    {
    var stats = transferManager.GetStatistics();
    UpdateStatus($"统计: {stats}");
    }

    private void UpdateStatus(string message)
    {
    Dispatcher.Invoke(() =>
    {
    statusText.Text = $"{DateTime.Now:HH:mm:ss}{message}";
    });
    }

    // 菜单命令
    public void SaveSession(string filePath)
    {
    try
    {
    byte[] compressedData = transferManager.GetCompressedSessionData(currentSessionId);
    File.WriteAllBytes(filePath, compressedData);
    UpdateStatus($"会话已保存到: {filePath}");
    }
    catch (Exception ex)
    {
    MessageBox.Show($"保存失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
    }
    }

    public void LoadSession(string filePath)
    {
    try
    {
    byte[] compressedData = File.ReadAllBytes(filePath);
    transferManager.RestoreFromCompressedData(compressedData);

    // 清除当前画板并重新加载
    inkCanvas.Strokes.Clear();
    var strokes = transferManager.GetSessionStrokes(currentSessionId);
    foreach (var stroke in strokes)
    {
    inkCanvas.Strokes.Add(stroke);
    }

    UpdateStatus($"会话已从 {filePath} 加载");
    }
    catch (Exception ex)
    {
    MessageBox.Show($"加载失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
    }
    }

    public void ExportToImage(string filePath)
    {
    try
    {
    Rect bounds = inkCanvas.Strokes.GetBounds();
    RenderTargetBitmap rtb = new RenderTargetBitmap(
    (int)Math.Ceiling(bounds.Width),
    (int)Math.Ceiling(bounds.Height),
    96, 96, PixelFormats.Default);

    DrawingVisual dv = new DrawingVisual();
    using (DrawingContext dc = dv.RenderOpen())
    {
    VisualBrush vb = new VisualBrush(inkCanvas);
    dc.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
    }

    rtb.Render(dv);

    using (FileStream fs = new FileStream(filePath, FileMode.Create))
    {
    BitmapEncoder encoder = new PngBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(rtb));
    encoder.Save(fs);
    }

    UpdateStatus($"已导出图像到: {filePath}");
    }
    catch (Exception ex)
    {
    MessageBox.Show($"导出失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
    }
    }
    }

    // 状态装饰器
    public class StatusAdorner : Adorner
    {
    private readonly TextBlock statusText;

    public StatusAdorner(UIElement adornedElement, TextBlock statusText)
    : base(adornedElement)
    {
    this.statusText = statusText;
    AddVisualChild(statusText);
    }

    protected override int VisualChildrenCount => 1;

    protected override Visual GetVisualChild(int index)
    {
    if (index != 0) throw new ArgumentOutOfRangeException();
    return statusText;
    }

    protected override Size MeasureOverride(Size constraint)
    {
    statusText.Measure(constraint);
    return constraint;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
    statusText.Arrange(new Rect(0, finalSize.Height 40, finalSize.Width, 40));
    return finalSize;
    }
    }

    // 测试应用程序
    public class CollaborationWhiteboardApp
    {
    [STAThread]
    public static void Main()
    {
    Application app = new Application();

    NetworkCollaborationWhiteboard whiteboard = new NetworkCollaborationWhiteboard
    {
    Title = "网络协作白板 – 优化的墨迹传输",
    Width = 1000,
    Height = 700,
    WindowStartupLocation = WindowStartupLocation.CenterScreen
    };

    // 添加菜单
    Menu mainMenu = new Menu();
    DockPanel.SetDock(mainMenu, Dock.Top);

    // 文件菜单
    MenuItem fileMenu = new MenuItem { Header = "文件(_F)" };

    MenuItem saveMenuItem = new MenuItem
    {
    Header = "保存会话(_S)",
    InputGestureText = "Ctrl+S"
    };
    saveMenuItem.Click += (s, e) =>
    {
    var dialog = new Microsoft.Win32.SaveFileDialog
    {
    Filter = "白板会话文件 (*.wbx)|*.wbx|所有文件 (*.*)|*.*",
    DefaultExt = ".wbx"
    };

    if (dialog.ShowDialog() == true)
    {
    whiteboard.SaveSession(dialog.FileName);
    }
    };

    MenuItem loadMenuItem = new MenuItem
    {
    Header = "加载会话(_L)",
    InputGestureText = "Ctrl+L"
    };
    loadMenuItem.Click += (s, e) =>
    {
    var dialog = new Microsoft.Win32.OpenFileDialog
    {
    Filter = "白板会话文件 (*.wbx)|*.wbx|所有文件 (*.*)|*.*",
    DefaultExt = ".wbx"
    };

    if (dialog.ShowDialog() == true)
    {
    whiteboard.LoadSession(dialog.FileName);
    }
    };

    MenuItem exportMenuItem = new MenuItem
    {
    Header = "导出为图像(_E)",
    InputGestureText = "Ctrl+E"
    };
    exportMenuItem.Click += (s, e) =>
    {
    var dialog = new Microsoft.Win32.SaveFileDialog
    {
    Filter = "PNG图像 (*.png)|*.png|JPEG图像 (*.jpg)|*.jpg|所有文件 (*.*)|*.*",
    DefaultExt = ".png"
    };

    if (dialog.ShowDialog() == true)
    {
    whiteboard.ExportToImage(dialog.FileName);
    }
    };

    MenuItem exitMenuItem = new MenuItem
    {
    Header = "退出(_X)",
    InputGestureText = "Alt+F4"
    };
    exitMenuItem.Click += (s, e) => app.Shutdown();

    fileMenu.Items.Add(saveMenuItem);
    fileMenu.Items.Add(loadMenuItem);
    fileMenu.Items.Add(new Separator());
    fileMenu.Items.Add(exportMenuItem);
    fileMenu.Items.Add(new Separator());
    fileMenu.Items.Add(exitMenuItem);

    // 工具菜单
    MenuItem toolsMenu = new MenuItem { Header = "工具(_T)" };

    MenuItem clearMenuItem = new MenuItem { Header = "清空白板(_C)" };
    clearMenuItem.Click += (s, e) =>
    {
    if (whiteboard.Content is InkCanvas canvas)
    {
    canvas.Strokes.Clear();
    }
    };

    toolsMenu.Items.Add(clearMenuItem);

    mainMenu.Items.Add(fileMenu);
    mainMenu.Items.Add(toolsMenu);

    // 创建主布局
    DockPanel mainPanel = new DockPanel();
    mainPanel.Children.Add(mainMenu);
    mainPanel.Children.Add(whiteboard.Content as UIElement);

    Window mainWindow = new Window
    {
    Title = "网络协作白板",
    Content = mainPanel,
    Width = 1000,
    Height = 700
    };

    // 添加快捷键
    mainWindow.InputBindings.Add(new KeyBinding(
    new RelayCommand(() => saveMenuItem.RaiseEvent(new RoutedEventArgs(MenuItem.ClickEvent))),
    Key.S, ModifierKeys.Control));

    mainWindow.InputBindings.Add(new KeyBinding(
    new RelayCommand(() => loadMenuItem.RaiseEvent(new RoutedEventArgs(MenuItem.ClickEvent))),
    Key.L, ModifierKeys.Control));

    app.Run(mainWindow);
    }
    }

    // 简单命令类
    public class RelayCommand : ICommand
    {
    private readonly Action execute;

    public event EventHandler CanExecuteChanged;

    public RelayCommand(Action execute)
    {
    this.execute = execute;
    }

    public bool CanExecute(object parameter) => true;

    public void Execute(object parameter)
    {
    execute?.Invoke();
    }
    }
    }

    3.2.4 自定义墨迹控件与动态绘图引擎

    对于专业级的网络绘图应用,标准的InkCanvas可能无法满足所有需求。创建自定义墨迹控件可以提供更大的灵活性和更好的性能。

    自定义墨迹控件的关键特性:

  • 双缓冲渲染:消除闪烁,提高渲染性能
  • 分层管理:支持多个绘图层
  • 撤销/重做系统:完整的操作历史
  • 自定义工具:扩展绘图工具集
  • 性能优化:大规模笔迹的高效渲染
  • 完整自定义墨迹绘图引擎:

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Shapes;

    namespace CustomInkDrawingEngine
    {
    // 绘图工具枚举
    public enum DrawingTool
    {
    Pen,
    Highlighter,
    Line,
    Rectangle,
    Ellipse,
    Polygon,
    Text,
    Eraser,
    Selector,
    Pan
    }

    // 绘图层
    public class DrawingLayer : INotifyPropertyChanged
    {
    private string name;
    private bool isVisible = true;
    private bool isLocked = false;
    private double opacity = 1.0;

    public StrokeCollection Strokes { get; }
    public List<Shape> Shapes { get; }
    public List<TextBlock> TextElements { get; }

    public string Name
    {
    get => name;
    set
    {
    if (name != value)
    {
    name = value;
    OnPropertyChanged(nameof(Name));
    }
    }
    }

    public bool IsVisible
    {
    get => isVisible;
    set
    {
    if (isVisible != value)
    {
    isVisible = value;
    OnPropertyChanged(nameof(IsVisible));
    }
    }
    }

    public bool IsLocked
    {
    get => isLocked;
    set
    {
    if (isLocked != value)
    {
    isLocked = value;
    OnPropertyChanged(nameof(IsLocked));
    }
    }
    }

    public double Opacity
    {
    get => opacity;
    set
    {
    if (Math.Abs(opacity value) > 0.001)
    {
    opacity = Math.Max(0, Math.Min(1, value));
    OnPropertyChanged(nameof(Opacity));
    }
    }
    }

    public DrawingLayer(string layerName)
    {
    Name = layerName;
    Strokes = new StrokeCollection();
    Shapes = new List<Shape>();
    TextElements = new List<TextBlock>();

    Strokes.StrokesChanged += OnStrokesChanged;
    }

    private void OnStrokesChanged(object sender, StrokeCollectionChangedEventArgs e)
    {
    OnPropertyChanged(nameof(Strokes));
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    }

    // 绘图历史记录项
    public class DrawingHistoryItem
    {
    public string Description { get; set; }
    public DrawingAction Action { get; set; }
    public object Data { get; set; }
    public DateTime Timestamp { get; set; }

    public DrawingHistoryItem(string description, DrawingAction action, object data)
    {
    Description = description;
    Action = action;
    Data = data;
    Timestamp = DateTime.Now;
    }
    }

    // 绘图动作枚举
    public enum DrawingAction
    {
    StrokeAdded,
    StrokeRemoved,
    StrokeModified,
    ShapeAdded,
    ShapeRemoved,
    ShapeModified,
    TextAdded,
    TextRemoved,
    TextModified,
    LayerAdded,
    LayerRemoved,
    LayerModified
    }

    // 自定义墨迹画板
    public class CustomInkCanvas : Control
    {
    // 依赖属性
    public static readonly DependencyProperty CurrentToolProperty =
    DependencyProperty.Register("CurrentTool", typeof(DrawingTool), typeof(CustomInkCanvas),
    new FrameworkPropertyMetadata(DrawingTool.Pen, OnCurrentToolChanged));

    public static readonly DependencyProperty DrawingAttributesProperty =
    DependencyProperty.Register("DrawingAttributes", typeof(DrawingAttributes), typeof(CustomInkCanvas),
    new FrameworkPropertyMetadata(new DrawingAttributes(), OnDrawingAttributesChanged));

    public static readonly DependencyProperty SelectedColorProperty =
    DependencyProperty.Register("SelectedColor", typeof(Color), typeof(CustomInkCanvas),
    new FrameworkPropertyMetadata(Colors.Black, OnSelectedColorChanged));

    public static readonly DependencyProperty StrokeThicknessProperty =
    DependencyProperty.Register("StrokeThickness", typeof(double), typeof(CustomInkCanvas),
    new FrameworkPropertyMetadata(3.0, OnStrokeThicknessChanged));

    public static readonly DependencyProperty IsPressureSensitiveProperty =
    DependencyProperty.Register("IsPressureSensitive", typeof(bool), typeof(CustomInkCanvas),
    new FrameworkPropertyMetadata(true));

    // 绘图状态
    private DrawingLayer currentLayer;
    private Stroke currentStroke;
    private StylusPointCollection currentPoints;
    private Point startPoint;
    private Shape currentShape;
    private bool isDrawing = false;
    private bool isDragging = false;
    private Point dragStartPoint;

    // 视觉元素
    private DrawingVisual drawingVisual;
    private DrawingContext drawingContext;

    // 历史记录
    private readonly Stack<DrawingHistoryItem> undoStack = new Stack<DrawingHistoryItem>();
    private readonly Stack<DrawingHistoryItem> redoStack = new Stack<DrawingHistoryItem>();
    private readonly int maxHistorySize = 100;

    // 图层管理
    private readonly ObservableCollection<DrawingLayer> layers = new ObservableCollection<DrawingLayer>();
    private int currentLayerIndex = 0;

    // 选择状态
    private readonly List<Stroke> selectedStrokes = new List<Stroke>();
    private readonly List<Shape> selectedShapes = new List<Shape>();

    static CustomInkCanvas()
    {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomInkCanvas),
    new FrameworkPropertyMetadata(typeof(CustomInkCanvas)));

    BackgroundProperty.OverrideMetadata(typeof(CustomInkCanvas),
    new FrameworkPropertyMetadata(Brushes.White));
    }

    public CustomInkCanvas()
    {
    // 创建默认图层
    DrawingLayer defaultLayer = new DrawingLayer("图层 1");
    layers.Add(defaultLayer);
    currentLayer = defaultLayer;

    // 创建绘图视觉对象
    drawingVisual = new DrawingVisual();
    AddVisualChild(drawingVisual);
    AddLogicalChild(drawingVisual);

    // 设置输入处理
    Focusable = true;
    IsTabStop = true;

    SetupInputHandling();
    SetupCommandBindings();

    // 监听图层变化
    layers.CollectionChanged += OnLayersCollectionChanged;
    }

    // 属性
    public DrawingTool CurrentTool
    {
    get => (DrawingTool)GetValue(CurrentToolProperty);
    set => SetValue(CurrentToolProperty, value);
    }

    public DrawingAttributes DrawingAttributes
    {
    get => (DrawingAttributes)GetValue(DrawingAttributesProperty);
    set => SetValue(DrawingAttributesProperty, value);
    }

    public Color SelectedColor
    {
    get => (Color)GetValue(SelectedColorProperty);
    set => SetValue(SelectedColorProperty, value);
    }

    public double StrokeThickness
    {
    get => (double)GetValue(StrokeThicknessProperty);
    set => SetValue(StrokeThicknessProperty, value);
    }

    public bool IsPressureSensitive
    {
    get => (bool)GetValue(IsPressureSensitiveProperty);
    set => SetValue(IsPressureSensitiveProperty, value);
    }

    public ObservableCollection<DrawingLayer> Layers => layers;

    public DrawingLayer CurrentLayer
    {
    get => currentLayer;
    set
    {
    if (currentLayer != value && value != null)
    {
    currentLayer = value;
    currentLayerIndex = layers.IndexOf(value);
    InvalidateVisual();
    }
    }
    }

    // 输入处理设置
    private void SetupInputHandling()
    {
    // 鼠标事件
    MouseDown += OnMouseDown;
    MouseMove += OnMouseMove;
    MouseUp += OnMouseUp;
    MouseLeave += OnMouseLeave;

    // 触笔事件
    StylusDown += OnStylusDown;
    StylusMove += OnStylusMove;
    StylusUp += OnStylusUp;
    StylusSystemGesture += OnStylusSystemGesture;

    // 键盘事件
    KeyDown += OnKeyDown;
    PreviewKeyDown += OnPreviewKeyDown;

    // 拖放
    AllowDrop = true;
    DragEnter += OnDragEnter;
    DragOver += OnDragOver;
    DragLeave += OnDragLeave;
    Drop += OnDrop;
    }

    private void SetupCommandBindings()
    {
    // 撤销/重做命令
    CommandBindings.Add(new CommandBinding(ApplicationCommands.Undo, ExecuteUndo, CanExecuteUndo));
    CommandBindings.Add(new CommandBinding(ApplicationCommands.Redo, ExecuteRedo, CanExecuteRedo));

    // 复制/剪切/粘贴命令
    CommandBindings.Add(new CommandBinding(ApplicationCommands.Copy, ExecuteCopy, CanExecuteCopy));
    CommandBindings.Add(new CommandBinding(ApplicationCommands.Cut, ExecuteCut, CanExecuteCut));
    CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, ExecutePaste, CanExecutePaste));

    // 删除命令
    CommandBindings.Add(new CommandBinding(ApplicationCommands.Delete, ExecuteDelete, CanExecuteDelete));

    // 全选命令
    CommandBindings.Add(new CommandBinding(ApplicationCommands.SelectAll, ExecuteSelectAll));
    }

    // 属性更改回调
    private static void OnCurrentToolChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    var canvas = (CustomInkCanvas)d;
    canvas.UpdateCursor();
    }

    private static void OnDrawingAttributesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    var canvas = (CustomInkCanvas)d;
    // 可以在这里添加额外处理
    }

    private static void OnSelectedColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    var canvas = (CustomInkCanvas)d;
    var attributes = canvas.DrawingAttributes.Clone();
    attributes.Color = canvas.SelectedColor;
    canvas.DrawingAttributes = attributes;
    }

    private static void OnStrokeThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    var canvas = (CustomInkCanvas)d;
    var attributes = canvas.DrawingAttributes.Clone();
    attributes.Width = canvas.StrokeThickness;
    attributes.Height = canvas.StrokeThickness;
    canvas.DrawingAttributes = attributes;
    }

    // 鼠标事件处理
    private void OnMouseDown(object sender, MouseButtonEventArgs e)
    {
    Focus();

    if (e.StylusDevice != null && e.StylusDevice.IsValid)
    return; // 由触笔事件处理

    startPoint = e.GetPosition(this);

    switch (CurrentTool)
    {
    case DrawingTool.Pen:
    case DrawingTool.Highlighter:
    StartFreehandDrawing(startPoint, false);
    break;

    case DrawingTool.Line:
    case DrawingTool.Rectangle:
    case DrawingTool.Ellipse:
    StartShapeDrawing(startPoint);
    break;

    case DrawingTool.Selector:
    if (e.ChangedButton == MouseButton.Left)
    {
    StartSelection(startPoint);
    }
    break;

    case DrawingTool.Pan:
    StartPanning(startPoint);
    break;

    case DrawingTool.Eraser:
    StartErasing(startPoint);
    break;
    }

    CaptureMouse();
    e.Handled = true;
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
    if (!isDrawing && !isDragging)
    return;

    Point currentPoint = e.GetPosition(this);

    switch (CurrentTool)
    {
    case DrawingTool.Pen:
    case DrawingTool.Highlighter:
    ContinueFreehandDrawing(currentPoint, false);
    break;

    case DrawingTool.Line:
    case DrawingTool.Rectangle:
    case DrawingTool.Ellipse:
    UpdateShapeDrawing(currentPoint);
    break;

    case DrawingTool.Selector:
    if (isDragging)
    {
    UpdateSelection(currentPoint);
    }
    break;

    case DrawingTool.Pan:
    UpdatePanning(currentPoint);
    break;

    case DrawingTool.Eraser:
    ContinueErasing(currentPoint);
    break;
    }

    e.Handled = true;
    }

    private void OnMouseUp(object sender, MouseButtonEventArgs e)
    {
    if (!isDrawing && !isDragging)
    return;

    Point endPoint = e.GetPosition(this);

    switch (CurrentTool)
    {
    case DrawingTool.Pen:
    case DrawingTool.Highlighter:
    EndFreehandDrawing(endPoint);
    break;

    case DrawingTool.Line:
    case DrawingTool.Rectangle:
    case DrawingTool.Ellipse:
    EndShapeDrawing(endPoint);
    break;

    case DrawingTool.Selector:
    EndSelection(endPoint);
    break;

    case DrawingTool.Pan:
    EndPanning(endPoint);
    break;

    case DrawingTool.Eraser:
    EndErasing(endPoint);
    break;
    }

    ReleaseMouseCapture();
    e.Handled = true;
    }

    private void OnMouseLeave(object sender, MouseEventArgs e)
    {
    if (isDrawing)
    {
    // 如果鼠标离开画布,结束当前绘图
    Point currentPoint = e.GetPosition(this);
    EndFreehandDrawing(currentPoint);
    }
    }

    // 触笔事件处理
    private void OnStylusDown(object sender, StylusDownEventArgs e)
    {
    Focus();

    startPoint = e.GetPosition(this);

    switch (CurrentTool)
    {
    case DrawingTool.Pen:
    case DrawingTool.Highlighter:
    StartFreehandDrawing(startPoint, true);
    break;

    case DrawingTool.Eraser:
    StartErasing(startPoint);
    break;
    }

    Stylus.Capture(this);
    e.Handled = true;
    }

    private void OnStylusMove(object sender, StylusEventArgs e)
    {
    if (!isDrawing)
    return;

    Point currentPoint = e.GetPosition(this);

    switch (CurrentTool)
    {
    case DrawingTool.Pen:
    case DrawingTool.Highlighter:
    ContinueFreehandDrawing(currentPoint, true);
    break;

    case DrawingTool.Eraser:
    ContinueErasing(currentPoint);
    break;
    }

    e.Handled = true;
    }

    private void OnStylusUp(object sender, StylusEventArgs e)
    {
    if (!isDrawing)
    return;

    Point endPoint = e.GetPosition(this);

    switch (CurrentTool)
    {
    case DrawingTool.Pen:
    case DrawingTool.Highlighter:
    EndFreehandDrawing(endPoint);
    break;

    case DrawingTool.Eraser:
    EndErasing(endPoint);
    break;
    }

    Stylus.Capture(null);
    e.Handled = true;
    }

    private void OnStylusSystemGesture(object sender, StylusSystemGestureEventArgs e)
    {
    // 处理触笔系统手势
    Debug.WriteLine($"触笔系统手势: {e.SystemGesture}");
    e.Handled = true;
    }

    // 键盘事件处理
    private void OnKeyDown(object sender, KeyEventArgs e)
    {
    // 处理键盘快捷键
    switch (e.Key)
    {
    case Key.Escape:
    CancelCurrentOperation();
    break;

    case Key.Z when (Keyboard.Modifiers & ModifierKeys.Control) != 0:
    if ((Keyboard.Modifiers & ModifierKeys.Shift) != 0)
    Redo();
    else
    Undo();
    break;

    case Key.Y when (Keyboard.Modifiers & ModifierKeys.Control) != 0:
    Redo();
    break;

    case Key.Delete:
    DeleteSelection();
    break;

    case Key.A when (Keyboard.Modifiers & ModifierKeys.Control) != 0:
    SelectAll();
    break;
    }
    }

    private void OnPreviewKeyDown(object sender, KeyEventArgs e)
    {
    // 防止某些键的默认行为
    switch (e.Key)
    {
    case Key.Space:
    // 空格键用于平移工具
    if (!isDragging)
    {
    CurrentTool = DrawingTool.Pan;
    UpdateCursor();
    e.Handled = true;
    }
    break;
    }
    }

    // 拖放事件处理
    private void OnDragEnter(object sender, DragEventArgs e)
    {
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
    {
    e.Effects = DragDropEffects.Copy;
    }
    else
    {
    e.Effects = DragDropEffects.None;
    }
    e.Handled = true;
    }

    private void OnDragOver(object sender, DragEventArgs e)
    {
    e.Handled = true;
    }

    private void OnDragLeave(object sender, DragEventArgs e)
    {
    e.Handled = true;
    }

    private void OnDrop(object sender, DragEventArgs e)
    {
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
    {
    string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
    Point dropPoint = e.GetPosition(this);

    foreach (string file in files)
    {
    // 处理拖放的文件
    HandleDroppedFile(file, dropPoint);
    }
    }
    e.Handled = true;
    }

    // 绘图操作
    private void StartFreehandDrawing(Point startPoint, bool isStylus)
    {
    if (currentLayer.IsLocked)
    return;

    isDrawing = true;
    currentPoints = new StylusPointCollection();

    // 根据是否为荧光笔设置属性
    var attributes = DrawingAttributes.Clone();
    if (CurrentTool == DrawingTool.Highlighter)
    {
    attributes.IsHighlighter = true;
    attributes.Color = Color.FromArgb(128, attributes.Color.R, attributes.Color.G, attributes.Color.B);
    }

    // 压力敏感
    if (isStylus && IsPressureSensitive)
    {
    // 触笔输入,使用压力数据
    currentPoints.Add(new StylusPoint(startPoint.X, startPoint.Y, 0.5f));
    }
    else
    {
    // 鼠标输入,使用固定压力
    currentPoints.Add(new StylusPoint(startPoint.X, startPoint.Y, 0.5f));
    }

    currentStroke = new Stroke(currentPoints, attributes);
    }

    private void ContinueFreehandDrawing(Point currentPoint, bool isStylus)
    {
    if (!isDrawing || currentStroke == null)
    return;

    // 压力敏感
    float pressure = isStylus && IsPressureSensitive ? 0.5f : 0.5f; // 简化实现

    currentPoints.Add(new StylusPoint(currentPoint.X, currentPoint.Y, pressure));

    // 动态调整笔画粗细(基于压力)
    if (IsPressureSensitive && pressure > 0)
    {
    double baseWidth = DrawingAttributes.Width;
    double adjustedWidth = baseWidth * (0.5 + pressure * 0.5);

    if (Math.Abs(currentStroke.DrawingAttributes.Width adjustedWidth) > 0.1)
    {
    var adjustedAttributes = currentStroke.DrawingAttributes.Clone();
    adjustedAttributes.Width = adjustedWidth;
    adjustedAttributes.Height = adjustedWidth;
    currentStroke.DrawingAttributes = adjustedAttributes;
    }
    }

    InvalidateVisual();
    }

    private void EndFreehandDrawing(Point endPoint)
    {
    if (!isDrawing || currentStroke == null)
    return;

    // 添加最后一个点
    currentPoints.Add(new StylusPoint(endPoint.X, endPoint.Y, 0.5f));

    // 确保至少有两个点
    if (currentPoints.Count >= 2)
    {
    // 添加到当前图层
    currentLayer.Strokes.Add(currentStroke);

    // 添加到历史记录
    AddToHistory(new DrawingHistoryItem(
    "添加笔画",
    DrawingAction.StrokeAdded,
    currentStroke.Clone()));

    // 触发事件
    RaiseStrokeCollectedEvent(currentStroke);
    }

    // 清理
    currentStroke = null;
    currentPoints = null;
    isDrawing = false;

    InvalidateVisual();
    }

    private void StartShapeDrawing(Point startPoint)
    {
    if (currentLayer.IsLocked)
    return;

    isDrawing = true;
    this.startPoint = startPoint;

    // 创建临时形状
    switch (CurrentTool)
    {
    case DrawingTool.Line:
    currentShape = new Line
    {
    Stroke = new SolidColorBrush(DrawingAttributes.Color),
    StrokeThickness = DrawingAttributes.Width,
    X1 = startPoint.X,
    Y1 = startPoint.Y,
    X2 = startPoint.X,
    Y2 = startPoint.Y,
    StrokeStartLineCap = PenLineCap.Round,
    StrokeEndLineCap = PenLineCap.Round
    };
    break;

    case DrawingTool.Rectangle:
    currentShape = new Rectangle
    {
    Stroke = new SolidColorBrush(DrawingAttributes.Color),
    StrokeThickness = DrawingAttributes.Width,
    Fill = Brushes.Transparent,
    Width = 0,
    Height = 0,
    RenderTransform = new TranslateTransform(startPoint.X, startPoint.Y)
    };
    break;

    case DrawingTool.Ellipse:
    currentShape = new Ellipse
    {
    Stroke = new SolidColorBrush(DrawingAttributes.Color),
    StrokeThickness = DrawingAttributes.Width,
    Fill = Brushes.Transparent,
    Width = 0,
    Height = 0,
    RenderTransform = new TranslateTransform(startPoint.X, startPoint.Y)
    };
    break;
    }

    // 添加到临时渲染层
    if (currentShape != null)
    {
    currentShape.SetValue(ZIndexProperty, 1000);
    AddVisualChild(currentShape);
    AddLogicalChild(currentShape);
    }
    }

    private void UpdateShapeDrawing(Point currentPoint)
    {
    if (!isDrawing || currentShape == null)
    return;

    double width = Math.Abs(currentPoint.X startPoint.X);
    double height = Math.Abs(currentPoint.Y startPoint.Y);
    double x = Math.Min(startPoint.X, currentPoint.X);
    double y = Math.Min(startPoint.Y, currentPoint.Y);

    switch (CurrentTool)
    {
    case DrawingTool.Line:
    var line = (Line)currentShape;
    line.X2 = currentPoint.X;
    line.Y2 = currentPoint.Y;
    break;

    case DrawingTool.Rectangle:
    var rect = (Rectangle)currentShape;
    rect.Width = width;
    rect.Height = height;
    ((TranslateTransform)rect.RenderTransform).X = x;
    ((TranslateTransform)rect.RenderTransform).Y = y;
    break;

    case DrawingTool.Ellipse:
    var ellipse = (Ellipse)currentShape;
    ellipse.Width = width;
    ellipse.Height = height;
    ((TranslateTransform)ellipse.RenderTransform).X = x;
    ((TranslateTransform)ellipse.RenderTransform).Y = y;
    break;
    }
    }

    private void EndShapeDrawing(Point endPoint)
    {
    if (!isDrawing || currentShape == null)
    return;

    // 确保形状有有效尺寸
    double width = Math.Abs(endPoint.X startPoint.X);
    double height = Math.Abs(endPoint.Y startPoint.Y);

    if (width > 1 || height > 1) // 最小尺寸检查
    {
    // 添加到当前图层
    currentLayer.Shapes.Add(currentShape);

    // 添加到历史记录
    AddToHistory(new DrawingHistoryItem(
    $"添加{CurrentTool}形状",
    DrawingAction.ShapeAdded,
    CloneShape(currentShape)));

    // 从临时层移除
    RemoveVisualChild(currentShape);
    RemoveLogicalChild(currentShape);

    // 添加到永久渲染
    currentShape.SetValue(ZIndexProperty, currentLayerIndex);
    AddVisualChild(currentShape);
    AddLogicalChild(currentShape);

    // 触发事件
    RaiseShapeAddedEvent(currentShape);
    }
    else
    {
    // 移除临时形状
    RemoveVisualChild(currentShape);
    RemoveLogicalChild(currentShape);
    }

    currentShape = null;
    isDrawing = false;
    }

    // 选择操作
    private void StartSelection(Point startPoint)
    {
    this.startPoint = startPoint;
    isDragging = true;
    dragStartPoint = startPoint;

    // 清除之前的选择
    ClearSelection();
    }

    private void UpdateSelection(Point currentPoint)
    {
    if (!isDragging)
    return;

    // 绘制选择矩形
    DrawSelectionRectangle(startPoint, currentPoint);
    }

    private void EndSelection(Point endPoint)
    {
    if (!isDragging)
    return;

    isDragging = false;

    // 计算选择区域
    Rect selectionRect = new Rect(
    Math.Min(startPoint.X, endPoint.X),
    Math.Min(startPoint.Y, endPoint.Y),
    Math.Abs(endPoint.X startPoint.X),
    Math.Abs(endPoint.Y startPoint.Y));

    // 选择区域内的笔画
    foreach (Stroke stroke in currentLayer.Strokes)
    {
    if (selectionRect.IntersectsWith(stroke.GetBounds()))
    {
    selectedStrokes.Add(stroke);
    HighlightStroke(stroke, true);
    }
    }

    // 选择区域内的形状
    foreach (Shape shape in currentLayer.Shapes)
    {
    Rect shapeBounds = VisualTreeHelper.GetDescendantBounds(shape);
    if (selectionRect.IntersectsWith(shapeBounds))
    {
    selectedShapes.Add(shape);
    HighlightShape(shape, true);
    }
    }

    // 清除选择矩形
    ClearSelectionRectangle();

    RaiseSelectionChangedEvent();
    }

    private void DrawSelectionRectangle(Point start, Point end)
    {
    // 在实际实现中,需要绘制选择矩形
    // 这里简化为更新视觉
    InvalidateVisual();
    }

    private void ClearSelectionRectangle()
    {
    // 清除选择矩形
    InvalidateVisual();
    }

    // 平移操作
    private void StartPanning(Point startPoint)
    {
    isDragging = true;
    dragStartPoint = startPoint;
    Cursor = Cursors.Hand;
    }

    private void UpdatePanning(Point currentPoint)
    {
    if (!isDragging)
    return;

    // 计算平移距离
    double deltaX = currentPoint.X dragStartPoint.X;
    double deltaY = currentPoint.Y dragStartPoint.Y;

    // 应用平移变换
    ApplyPanTransform(deltaX, deltaY);

    dragStartPoint = currentPoint;
    }

    private void EndPanning(Point endPoint)
    {
    isDragging = false;
    UpdateCursor();
    }

    private void ApplyPanTransform(double deltaX, double deltaY)
    {
    // 在实际实现中,需要应用变换到所有可视元素
    // 这里简化为提示
    Debug.WriteLine($"平移: {deltaX}, {deltaY}");
    }

    // 擦除操作
    private void StartErasing(Point startPoint)
    {
    isDrawing = true;
    EraseAtPoint(startPoint);
    }

    private void ContinueErasing(Point currentPoint)
    {
    if (!isDrawing)
    return;

    EraseAtPoint(currentPoint);
    }

    private void EndErasing(Point endPoint)
    {
    isDrawing = false;
    }

    private void EraseAtPoint(Point point)
    {
    double eraserRadius = DrawingAttributes.Width * 2;
    Rect eraserRect = new Rect(
    point.X eraserRadius,
    point.Y eraserRadius,
    eraserRadius * 2,
    eraserRadius * 2);

    // 查找并移除相交的笔画
    List<Stroke> strokesToRemove = new List<Stroke>();

    foreach (Stroke stroke in currentLayer.Strokes)
    {
    if (eraserRect.IntersectsWith(stroke.GetBounds()))
    {
    // 检查实际交点(简化实现)
    bool intersects = false;
    for (int i = 0; i < stroke.StylusPoints.Count 1; i++)
    {
    Point p1 = new Point(stroke.StylusPoints[i].X, stroke.StylusPoints[i].Y);
    Point p2 = new Point(stroke.StylusPoints[i + 1].X, stroke.StylusPoints[i + 1].Y);

    if (DistanceFromPointToLineSegment(point, p1, p2) < eraserRadius)
    {
    intersects = true;
    break;
    }
    }

    if (intersects)
    {
    strokesToRemove.Add(stroke);
    }
    }
    }

    // 移除笔画
    foreach (Stroke stroke in strokesToRemove)
    {
    currentLayer.Strokes.Remove(stroke);
    AddToHistory(new DrawingHistoryItem(
    "擦除笔画",
    DrawingAction.StrokeRemoved,
    stroke.Clone()));
    }

    if (strokesToRemove.Count > 0)
    {
    InvalidateVisual();
    }
    }

    // 辅助方法
    private double DistanceFromPointToLineSegment(Point point, Point lineStart, Point lineEnd)
    {
    double lineLengthSquared = (lineEnd.X lineStart.X) * (lineEnd.X lineStart.X) +
    (lineEnd.Y lineStart.Y) * (lineEnd.Y lineStart.Y);

    if (lineLengthSquared == 0)
    return Distance(point, lineStart);

    double t = Math.Max(0, Math.Min(1,
    ((point.X lineStart.X) * (lineEnd.X lineStart.X) +
    (point.Y lineStart.Y) * (lineEnd.Y lineStart.Y)) / lineLengthSquared));

    Point projection = new Point(
    lineStart.X + t * (lineEnd.X lineStart.X),
    lineStart.Y + t * (lineEnd.Y lineStart.Y));

    return Distance(point, projection);
    }

    private double Distance(Point p1, Point p2)
    {
    double dx = p2.X p1.X;
    double dy = p2.Y p1.Y;
    return Math.Sqrt(dx * dx + dy * dy);
    }

    private void UpdateCursor()
    {
    switch (CurrentTool)
    {
    case DrawingTool.Pen:
    Cursor = Cursors.Pen;
    break;
    case DrawingTool.Highlighter:
    Cursor = Cursors.Pen;
    break;
    case DrawingTool.Line:
    case DrawingTool.Rectangle:
    case DrawingTool.Ellipse:
    Cursor = Cursors.Cross;
    break;
    case DrawingTool.Selector:
    Cursor = Cursors.Arrow;
    break;
    case DrawingTool.Pan:
    Cursor = Cursors.Hand;
    break;
    case DrawingTool.Eraser:
    Cursor = Cursors.UpArrow; // 或自定义橡皮擦光标
    break;
    default:
    Cursor = Cursors.Arrow;
    break;
    }
    }

    private void CancelCurrentOperation()
    {
    if (isDrawing)
    {
    // 取消当前绘图
    isDrawing = false;

    if (currentStroke != null)
    {
    currentStroke = null;
    currentPoints = null;
    }

    if (currentShape != null)
    {
    RemoveVisualChild(currentShape);
    RemoveLogicalChild(currentShape);
    currentShape = null;
    }

    InvalidateVisual();
    }

    if (isDragging)
    {
    // 取消当前拖拽
    isDragging = false;
    UpdateCursor();
    ClearSelectionRectangle();
    }
    }

    // 选择管理
    private void ClearSelection()
    {
    // 取消高亮
    foreach (Stroke stroke in selectedStrokes)
    {
    HighlightStroke(stroke, false);
    }

    foreach (Shape shape in selectedShapes)
    {
    HighlightShape(shape, false);
    }

    selectedStrokes.Clear();
    selectedShapes.Clear();

    RaiseSelectionChangedEvent();
    }

    private void HighlightStroke(Stroke stroke, bool highlight)
    {
    // 在实际实现中,需要修改笔画外观以显示选中状态
    // 这里简化为调试输出
    Debug.WriteLine($"{(highlight ? "高亮" : "取消高亮")}笔画");
    }

    private void HighlightShape(Shape shape, bool highlight)
    {
    if (highlight)
    {
    shape.Effect = new System.Windows.Media.Effects.DropShadowEffect
    {
    Color = Colors.Blue,
    ShadowDepth = 0,
    BlurRadius = 10
    };
    }
    else
    {
    shape.Effect = null;
    }
    }

    private void DeleteSelection()
    {
    if (selectedStrokes.Count == 0 && selectedShapes.Count == 0)
    return;

    // 创建历史记录
    var historyItem = new DrawingHistoryItem(
    "删除选择",
    DrawingAction.StrokeRemoved, // 简化为笔画删除
    new
    {
    Strokes = selectedStrokes.Select(s => s.Clone()).ToList(),
    Shapes = selectedShapes.Select(CloneShape).ToList()
    });

    // 从图层中移除
    foreach (Stroke stroke in selectedStrokes)
    {
    currentLayer.Strokes.Remove(stroke);
    }

    foreach (Shape shape in selectedShapes)
    {
    currentLayer.Shapes.Remove(shape);
    RemoveVisualChild(shape);
    RemoveLogicalChild(shape);
    }

    // 添加到历史记录
    AddToHistory(historyItem);

    // 清理选择
    ClearSelection();

    InvalidateVisual();
    }

    private void SelectAll()
    {
    ClearSelection();

    selectedStrokes.AddRange(currentLayer.Strokes);
    selectedShapes.AddRange(currentLayer.Shapes);

    foreach (Stroke stroke in selectedStrokes)
    {
    HighlightStroke(stroke, true);
    }

    foreach (Shape shape in selectedShapes)
    {
    HighlightShape(shape, true);
    }

    RaiseSelectionChangedEvent();
    }

    // 历史记录管理
    private void AddToHistory(DrawingHistoryItem item)
    {
    undoStack.Push(item);

    // 限制历史记录大小
    if (undoStack.Count > maxHistorySize)
    {
    // 移除最旧的项目
    var tempStack = new Stack<DrawingHistoryItem>();
    while (undoStack.Count > maxHistorySize 1)
    {
    tempStack.Push(undoStack.Pop());
    }
    undoStack.Clear();
    while (tempStack.Count > 0)
    {
    undoStack.Push(tempStack.Pop());
    }
    }

    // 清除重做栈
    redoStack.Clear();

    RaiseHistoryChangedEvent();
    }

    public void Undo()
    {
    if (undoStack.Count == 0)
    return;

    var item = undoStack.Pop();
    redoStack.Push(item);

    // 执行撤销操作
    ExecuteUndoAction(item);

    RaiseHistoryChangedEvent();
    }

    public void Redo()
    {
    if (redoStack.Count == 0)
    return;

    var item = redoStack.Pop();
    undoStack.Push(item);

    // 执行重做操作
    ExecuteRedoAction(item);

    RaiseHistoryChangedEvent();
    }

    private void ExecuteUndoAction(DrawingHistoryItem item)
    {
    // 根据动作类型执行撤销
    switch (item.Action)
    {
    case DrawingAction.StrokeAdded:
    var stroke = item.Data as Stroke;
    if (stroke != null)
    {
    currentLayer.Strokes.Remove(stroke);
    }
    break;

    case DrawingAction.ShapeAdded:
    var shape = item.Data as Shape;
    if (shape != null)
    {
    currentLayer.Shapes.Remove(shape);
    RemoveVisualChild(shape);
    RemoveLogicalChild(shape);
    }
    break;

    // 其他动作类型的处理…
    }

    InvalidateVisual();
    }

    private void ExecuteRedoAction(DrawingHistoryItem item)
    {
    // 根据动作类型执行重做
    switch (item.Action)
    {
    case DrawingAction.StrokeAdded:
    var stroke = item.Data as Stroke;
    if (stroke != null)
    {
    currentLayer.Strokes.Add(stroke);
    }
    break;

    case DrawingAction.ShapeAdded:
    var shape = item.Data as Shape;
    if (shape != null)
    {
    currentLayer.Shapes.Add(shape);
    AddVisualChild(shape);
    AddLogicalChild(shape);
    }
    break;

    // 其他动作类型的处理…
    }

    InvalidateVisual();
    }

    public bool CanUndo => undoStack.Count > 0;
    public bool CanRedo => redoStack.Count > 0;

    // 命令执行
    private void ExecuteUndo(object sender, ExecutedRoutedEventArgs e)
    {
    Undo();
    e.Handled = true;
    }

    private void CanExecuteUndo(object sender, CanExecuteRoutedEventArgs e)
    {
    e.CanExecute = CanUndo;
    e.Handled = true;
    }

    private void ExecuteRedo(object sender, ExecutedRoutedEventArgs e)
    {
    Redo();
    e.Handled = true;
    }

    private void CanExecuteRedo(object sender, CanExecuteRoutedEventArgs e)
    {
    e.CanExecute = CanRedo;
    e.Handled = true;
    }

    private void ExecuteCopy(object sender, ExecutedRoutedEventArgs e)
    {
    CopySelectionToClipboard();
    e.Handled = true;
    }

    private void CanExecuteCopy(object sender, CanExecuteRoutedEventArgs e)
    {
    e.CanExecute = selectedStrokes.Count > 0 || selectedShapes.Count > 0;
    e.Handled = true;
    }

    private void ExecuteCut(object sender, ExecutedRoutedEventArgs e)
    {
    CopySelectionToClipboard();
    DeleteSelection();
    e.Handled = true;
    }

    private void CanExecuteCut(object sender, CanExecuteRoutedEventArgs e)
    {
    e.CanExecute = selectedStrokes.Count > 0 || selectedShapes.Count > 0;
    e.Handled = true;
    }

    private void ExecutePaste(object sender, ExecutedRoutedEventArgs e)
    {
    PasteFromClipboard();
    e.Handled = true;
    }

    private void CanExecutePaste(object sender, CanExecuteRoutedEventArgs e)
    {
    e.CanExecute = Clipboard.ContainsData("CustomInkData") || Clipboard.ContainsImage();
    e.Handled = true;
    }

    private void ExecuteDelete(object sender, ExecutedRoutedEventArgs e)
    {
    DeleteSelection();
    e.Handled = true;
    }

    private void CanExecuteDelete(object sender, CanExecuteRoutedEventArgs e)
    {
    e.CanExecute = selectedStrokes.Count > 0 || selectedShapes.Count > 0;
    e.Handled = true;
    }

    private void ExecuteSelectAll(object sender, ExecutedRoutedEventArgs e)
    {
    SelectAll();
    e.Handled = true;
    }

    // 剪贴板操作
    private void CopySelectionToClipboard()
    {
    if (selectedStrokes.Count == 0 && selectedShapes.Count == 0)
    return;

    // 创建自定义数据格式
    var dataObject = new DataObject();

    // 添加笔画数据
    if (selectedStrokes.Count > 0)
    {
    var strokeCollection = new StrokeCollection(selectedStrokes.Select(s => s.Clone()));
    dataObject.SetData("CustomInkData", strokeCollection);
    }

    // 添加图像数据
    try
    {
    Rect bounds = GetSelectionBounds();
    if (bounds.Width > 0 && bounds.Height > 0)
    {
    RenderTargetBitmap rtb = new RenderTargetBitmap(
    (int)Math.Ceiling(bounds.Width),
    (int)Math.Ceiling(bounds.Height),
    96, 96, PixelFormats.Default);

    DrawingVisual dv = new DrawingVisual();
    using (DrawingContext dc = dv.RenderOpen())
    {
    // 渲染选择内容
    foreach (Stroke stroke in selectedStrokes)
    {
    stroke.Draw(dc);
    }

    foreach (Shape shape in selectedShapes)
    {
    var geometry = shape.RenderedGeometry;
    dc.DrawGeometry(shape.Fill, new Pen(shape.Stroke, shape.StrokeThickness), geometry);
    }
    }

    rtb.Render(dv);

    // 转换为位图源
    BitmapSource bitmap = rtb;
    dataObject.SetImage(bitmap);
    }
    }
    catch (Exception ex)
    {
    Debug.WriteLine($"复制到剪贴板时出错: {ex.Message}");
    }

    Clipboard.SetDataObject(dataObject);
    }

    private void PasteFromClipboard()
    {
    try
    {
    IDataObject dataObject = Clipboard.GetDataObject();

    if (dataObject.GetDataPresent("CustomInkData"))
    {
    // 粘贴笔画数据
    var strokeCollection = dataObject.GetData("CustomInkData") as StrokeCollection;
    if (strokeCollection != null)
    {
    Point pastePosition = new Point(ActualWidth / 2, ActualHeight / 2);

    foreach (Stroke stroke in strokeCollection)
    {
    var newStroke = stroke.Clone();
    currentLayer.Strokes.Add(newStroke);
    }

    AddToHistory(new DrawingHistoryItem(
    "粘贴笔画",
    DrawingAction.StrokeAdded,
    strokeCollection));

    InvalidateVisual();
    }
    }
    else if (dataObject.GetDataPresent(DataFormats.Bitmap))
    {
    // 粘贴图像数据
    // 实现图像粘贴逻辑
    }
    }
    catch (Exception ex)
    {
    Debug.WriteLine($"从剪贴板粘贴时出错: {ex.Message}");
    }
    }

    private Rect GetSelectionBounds()
    {
    Rect bounds = Rect.Empty;

    foreach (Stroke stroke in selectedStrokes)
    {
    bounds.Union(stroke.GetBounds());
    }

    foreach (Shape shape in selectedShapes)
    {
    bounds.Union(VisualTreeHelper.GetDescendantBounds(shape));
    }

    return bounds;
    }

    // 图层管理
    private void OnLayersCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
    switch (e.Action)
    {
    case NotifyCollectionChangedAction.Add:
    foreach (DrawingLayer layer in e.NewItems)
    {
    AddToHistory(new DrawingHistoryItem(
    $"添加图层: {layer.Name}",
    DrawingAction.LayerAdded,
    layer.Name));
    }
    break;

    case NotifyCollectionChangedAction.Remove:
    foreach (DrawingLayer layer in e.OldItems)
    {
    AddToHistory(new DrawingHistoryItem(
    $"移除图层: {layer.Name}",
    DrawingAction.LayerRemoved,
    layer.Name));
    }
    break;
    }

    InvalidateVisual();
    }

    public void AddLayer(string layerName)
    {
    DrawingLayer newLayer = new DrawingLayer(layerName);
    layers.Add(newLayer);
    }

    public void RemoveLayer(DrawingLayer layer)
    {
    if (layers.Count <= 1)
    return; // 至少保留一个图层

    if (layer == currentLayer)
    {
    // 切换到另一个图层
    int index = layers.IndexOf(layer);
    currentLayer = layers[index > 0 ? index 1 : 1];
    }

    layers.Remove(layer);
    }

    // 文件处理
    private void HandleDroppedFile(string filePath, Point position)
    {
    string extension = System.IO.Path.GetExtension(filePath).ToLower();

    switch (extension)
    {
    case ".png":
    case ".jpg":
    case ".jpeg":
    case ".bmp":
    case ".gif":
    // 加载图像
    LoadImage(filePath, position);
    break;

    case ".isf":
    // 加载墨迹数据
    LoadInkData(filePath);
    break;

    default:
    Debug.WriteLine($"不支持的文件类型: {extension}");
    break;
    }
    }

    private void LoadImage(string filePath, Point position)
    {
    try
    {
    BitmapImage bitmap = new BitmapImage(new Uri(filePath));

    Image image = new Image
    {
    Source = bitmap,
    Width = bitmap.PixelWidth,
    Height = bitmap.PixelHeight,
    RenderTransform = new TranslateTransform(position.X, position.Y)
    };

    // 添加到当前图层
    currentLayer.Shapes.Add(image);

    AddToHistory(new DrawingHistoryItem(
    "添加图像",
    DrawingAction.ShapeAdded,
    CloneShape(image)));

    // 添加到视觉树
    image.SetValue(ZIndexProperty, currentLayerIndex);
    AddVisualChild(image);
    AddLogicalChild(image);

    InvalidateVisual();
    }
    catch (Exception ex)
    {
    Debug.WriteLine($"加载图像时出错: {ex.Message}");
    }
    }

    private void LoadInkData(string filePath)
    {
    try
    {
    using (var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Open))
    {
    StrokeCollection strokes = new StrokeCollection(stream);

    foreach (Stroke stroke in strokes)
    {
    currentLayer.Strokes.Add(stroke.Clone());
    }

    AddToHistory(new DrawingHistoryItem(
    "加载墨迹数据",
    DrawingAction.StrokeAdded,
    strokes));

    InvalidateVisual();
    }
    }
    catch (Exception ex)
    {
    Debug.WriteLine($"加载墨迹数据时出错: {ex.Message}");
    }
    }

    // 保存功能
    public void SaveToFile(string filePath)
    {
    try
    {
    using (var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create))
    {
    // 收集所有图层的笔画
    StrokeCollection allStrokes = new StrokeCollection();
    foreach (var layer in layers)
    {
    allStrokes.Add(layer.Strokes);
    }

    allStrokes.Save(stream);
    }

    Debug.WriteLine($"已保存到: {filePath}");
    }
    catch (Exception ex)
    {
    Debug.WriteLine($"保存文件时出错: {ex.Message}");
    }
    }

    // 辅助方法
    private Shape CloneShape(Shape original)
    {
    // 简化实现 – 在实际应用中需要完整克隆形状
    if (original is Rectangle rect)
    {
    return new Rectangle
    {
    Width = rect.Width,
    Height = rect.Height,
    Stroke = rect.Stroke?.Clone(),
    Fill = rect.Fill?.Clone(),
    StrokeThickness = rect.StrokeThickness,
    RenderTransform = rect.RenderTransform?.Clone()
    };
    }
    else if (original is Ellipse ellipse)
    {
    return new Ellipse
    {
    Width = ellipse.Width,
    Height = ellipse.Height,
    Stroke = ellipse.Stroke?.Clone(),
    Fill = ellipse.Fill?.Clone(),
    StrokeThickness = ellipse.StrokeThickness,
    RenderTransform = ellipse.RenderTransform?.Clone()
    };
    }
    else if (original is Line line)
    {
    return new Line
    {
    X1 = line.X1,
    Y1 = line.Y1,
    X2 = line.X2,
    Y2 = line.Y2,
    Stroke = line.Stroke?.Clone(),
    StrokeThickness = line.StrokeThickness
    };
    }

    return null;
    }

    // 视觉树重写
    protected override int VisualChildrenCount => base.VisualChildrenCount +
    (drawingVisual != null ? 1 : 0) +
    (currentShape != null ? 1 : 0) +
    layers.Sum(l => l.Shapes.Count + l.TextElements.Count);

    protected override Visual GetVisualChild(int index)
    {
    int baseCount = base.VisualChildrenCount;

    if (index < baseCount)
    {
    return base.GetVisualChild(index);
    }

    index -= baseCount;

    if (drawingVisual != null && index == 0)
    {
    return drawingVisual;
    }

    if (drawingVisual != null)
    {
    index;
    }

    if (currentShape != null && index == 0)
    {
    return currentShape;
    }

    if (currentShape != null)
    {
    index;
    }

    // 返回图层中的形状和文本元素
    foreach (var layer in layers)
    {
    if (index < layer.Shapes.Count)
    {
    return layer.Shapes[index];
    }
    index -= layer.Shapes.Count;

    if (index < layer.TextElements.Count)
    {
    return layer.TextElements[index];
    }
    index -= layer.TextElements.Count;
    }

    throw new ArgumentOutOfRangeException(nameof(index));
    }

    // 渲染
    protected override void OnRender(DrawingContext drawingContext)
    {
    base.OnRender(drawingContext);

    // 清除绘图视觉对象
    using (var context = drawingVisual.RenderOpen())
    {
    // 渲染所有图层的笔画
    foreach (var layer in layers)
    {
    if (!layer.IsVisible || layer.Opacity <= 0)
    continue;

    // 设置图层不透明度
    if (layer.Opacity < 1.0)
    {
    context.PushOpacity(layer.Opacity);
    }

    // 渲染笔画
    foreach (Stroke stroke in layer.Strokes)
    {
    stroke.Draw(context);
    }

    if (layer.Opacity < 1.0)
    {
    context.Pop();
    }
    }

    // 渲染当前正在绘制的笔画
    if (currentStroke != null)
    {
    currentStroke.Draw(context);
    }
    }
    }

    // 事件
    public event EventHandler<StrokeEventArgs> StrokeCollected;
    public event EventHandler<ShapeEventArgs> ShapeAdded;
    public event EventHandler SelectionChanged;
    public event EventHandler HistoryChanged;

    protected virtual void RaiseStrokeCollectedEvent(Stroke stroke)
    {
    StrokeCollected?.Invoke(this, new StrokeEventArgs(stroke));
    }

    protected virtual void RaiseShapeAddedEvent(Shape shape)
    {
    ShapeAdded?.Invoke(this, new ShapeEventArgs(shape));
    }

    protected virtual void RaiseSelectionChangedEvent()
    {
    SelectionChanged?.Invoke(this, EventArgs.Empty);
    }

    protected virtual void RaiseHistoryChangedEvent()
    {
    HistoryChanged?.Invoke(this, EventArgs.Empty);
    }
    }

    // 事件参数类
    public class StrokeEventArgs : EventArgs
    {
    public Stroke Stroke { get; }

    public StrokeEventArgs(Stroke stroke)
    {
    Stroke = stroke;
    }
    }

    public class ShapeEventArgs : EventArgs
    {
    public Shape Shape { get; }

    public ShapeEventArgs(Shape shape)
    {
    Shape = shape;
    }
    }

    // 测试应用程序
    public class CustomInkCanvasTestApp
    {
    [STAThread]
    public static void Main()
    {
    Application app = new Application();

    Window mainWindow = new Window
    {
    Title = "自定义墨迹绘图引擎",
    Width = 1200,
    Height = 800,
    WindowStartupLocation = WindowStartupLocation.CenterScreen
    };

    // 创建主网格
    Grid mainGrid = new Grid();

    // 创建自定义墨迹画板
    CustomInkCanvas inkCanvas = new CustomInkCanvas();

    // 创建工具栏
    StackPanel toolbar = new StackPanel
    {
    Orientation = Orientation.Horizontal,
    Background = Brushes.LightGray,
    Height = 40,
    VerticalAlignment = VerticalAlignment.Top
    };

    // 工具选择
    ComboBox toolComboBox = new ComboBox
    {
    Width = 120,
    Margin = new Thickness(5),
    ItemsSource = Enum.GetValues(typeof(DrawingTool)),
    SelectedItem = DrawingTool.Pen
    };
    toolComboBox.SelectionChanged += (s, e) =>
    {
    if (toolComboBox.SelectedItem is DrawingTool tool)
    {
    inkCanvas.CurrentTool = tool;
    }
    };

    // 颜色选择
    ComboBox colorComboBox = new ComboBox
    {
    Width = 100,
    Margin = new Thickness(5),
    ItemsSource = new[]
    {
    Colors.Black,
    Colors.Red,
    Colors.Blue,
    Colors.Green,
    Colors.Purple,
    Colors.Orange,
    Colors.Yellow
    },
    SelectedIndex = 0
    };
    colorComboBox.SelectionChanged += (s, e) =>
    {
    if (colorComboBox.SelectedItem is Color color)
    {
    inkCanvas.SelectedColor = color;
    }
    };

    // 线宽调整
    Slider thicknessSlider = new Slider
    {
    Width = 100,
    Minimum = 1,
    Maximum = 20,
    Value = 3,
    Margin = new Thickness(5)
    };
    thicknessSlider.ValueChanged += (s, e) =>
    {
    inkCanvas.StrokeThickness = thicknessSlider.Value;
    };

    // 压力敏感开关
    CheckBox pressureCheckBox = new CheckBox
    {
    Content = "压力敏感",
    IsChecked = inkCanvas.IsPressureSensitive,
    Margin = new Thickness(5),
    VerticalAlignment = VerticalAlignment.Center
    };
    pressureCheckBox.Checked += (s, e) => inkCanvas.IsPressureSensitive = true;
    pressureCheckBox.Unchecked += (s, e) => inkCanvas.IsPressureSensitive = false;

    // 操作按钮
    Button undoButton = new Button
    {
    Content = "撤销",
    Margin = new Thickness(5),
    Padding = new Thickness(10, 5, 10, 5),
    Command = ApplicationCommands.Undo
    };

    Button redoButton = new Button
    {
    Content = "重做",
    Margin = new Thickness(5),
    Padding = new Thickness(10, 5, 10, 5),
    Command = ApplicationCommands.Redo
    };

    Button clearButton = new Button
    {
    Content = "清空",
    Margin = new Thickness(5),
    Padding = new Thickness(10, 5, 10, 5)
    };
    clearButton.Click += (s, e) =>
    {
    foreach (var layer in inkCanvas.Layers)
    {
    layer.Strokes.Clear();
    layer.Shapes.Clear();
    }
    inkCanvas.InvalidateVisual();
    };

    Button saveButton = new Button
    {
    Content = "保存",
    Margin = new Thickness(5),
    Padding = new Thickness(10, 5, 10, 5)
    };
    saveButton.Click += (s, e) =>
    {
    var dialog = new Microsoft.Win32.SaveFileDialog
    {
    Filter = "墨迹文件 (*.isf)|*.isf|所有文件 (*.*)|*.*",
    DefaultExt = ".isf"
    };

    if (dialog.ShowDialog() == true)
    {
    inkCanvas.SaveToFile(dialog.FileName);
    }
    };

    // 图层管理
    ComboBox layerComboBox = new ComboBox
    {
    Width = 120,
    Margin = new Thickness(5),
    ItemsSource = inkCanvas.Layers,
    DisplayMemberPath = "Name",
    SelectedItem = inkCanvas.CurrentLayer
    };
    layerComboBox.SelectionChanged += (s, e) =>
    {
    if (layerComboBox.SelectedItem is DrawingLayer layer)
    {
    inkCanvas.CurrentLayer = layer;
    }
    };

    Button addLayerButton = new Button
    {
    Content = "+",
    Width = 30,
    Margin = new Thickness(5),
    ToolTip = "添加新图层"
    };
    addLayerButton.Click += (s, e) =>
    {
    inkCanvas.AddLayer($"图层 {inkCanvas.Layers.Count + 1}");
    layerComboBox.Items.Refresh();
    };

    // 添加工具栏元素
    toolbar.Children.Add(new Label { Content = "工具:", VerticalAlignment = VerticalAlignment.Center });
    toolbar.Children.Add(toolComboBox);
    toolbar.Children.Add(new Label { Content = "颜色:", VerticalAlignment = VerticalAlignment.Center });
    toolbar.Children.Add(colorComboBox);
    toolbar.Children.Add(new Label { Content = "线宽:", VerticalAlignment = VerticalAlignment.Center });
    toolbar.Children.Add(thicknessSlider);
    toolbar.Children.Add(pressureCheckBox);
    toolbar.Children.Add(undoButton);
    toolbar.Children.Add(redoButton);
    toolbar.Children.Add(clearButton);
    toolbar.Children.Add(saveButton);
    toolbar.Children.Add(new Separator { Width = 20 });
    toolbar.Children.Add(new Label { Content = "图层:", VerticalAlignment = VerticalAlignment.Center });
    toolbar.Children.Add(layerComboBox);
    toolbar.Children.Add(addLayerButton);

    // 设置布局
    mainGrid.Children.Add(inkCanvas);
    mainGrid.Children.Add(toolbar);

    // 状态栏
    TextBlock statusBar = new TextBlock
    {
    Text = "就绪",
    Background = Brushes.LightGray,
    Padding = new Thickness(10),
    VerticalAlignment = VerticalAlignment.Bottom
    };

    inkCanvas.StrokeCollected += (s, e) =>
    {
    statusBar.Text = $"添加笔画,点数: {e.Stroke.StylusPoints.Count}";
    };

    inkCanvas.SelectionChanged += (s, e) =>
    {
    statusBar.Text = "选择已更改";
    };

    mainGrid.Children.Add(statusBar);

    mainWindow.Content = mainGrid;
    app.Run(mainWindow);
    }
    }
    }

    3.3 功能扩展建议与实践应用

    3.3.1 网络协作白板的扩展功能

    基于以上技术,可以构建功能完整的网络协作白板系统,具备以下扩展功能:

  • 实时协作:使用WebSocket或SignalR实现多用户实时协作
  • 版本控制:支持绘图历史版本的回滚和对比
  • 模板系统:预定义绘图模板(网络拓扑图、流程图等)
  • 导出格式:支持导出为SVG、PDF、PNG等多种格式
  • 标注工具:箭头、文字框、图形标注等专业工具
  • 录制回放:录制绘图过程并支持回放
  • 3.3.2 网络拓扑绘图工具

    结合网络编程知识,可以创建专业的网络拓扑绘图工具:

    // 简化的网络拓扑节点类
    public class NetworkNode
    {
    public string Id { get; set; }
    public string Name { get; set; }
    public Point Position { get; set; }
    public NetworkNodeType NodeType { get; set; }
    public List<NetworkConnection> Connections { get; set; } = new List<NetworkConnection>();
    public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>();
    }

    // 网络拓扑绘图控件
    public class NetworkTopologyCanvas : CustomInkCanvas
    {
    private readonly Dictionary<string, NetworkNode> nodes = new Dictionary<string, NetworkNode>();
    private readonly List<NetworkConnection> connections = new List<NetworkConnection>();

    public void AddNetworkNode(NetworkNode node)
    {
    nodes[node.Id] = node;
    DrawNode(node);
    }

    public void ConnectNodes(string nodeId1, string nodeId2, string connectionType)
    {
    if (nodes.TryGetValue(nodeId1, out var node1) &&
    nodes.TryGetValue(nodeId2, out var node2))
    {
    var connection = new NetworkConnection
    {
    Id = Guid.NewGuid().ToString(),
    SourceNode = node1,
    TargetNode = node2,
    ConnectionType = connectionType
    };

    connections.Add(connection);
    DrawConnection(connection);
    }
    }

    private void DrawNode(NetworkNode node)
    {
    // 根据节点类型绘制不同的图形
    Shape nodeShape = node.NodeType switch
    {
    NetworkNodeType.Router => CreateRouterShape(node),
    NetworkNodeType.Switch => CreateSwitchShape(node),
    NetworkNodeType.Server => CreateServerShape(node),
    NetworkNodeType.Client => CreateClientShape(node),
    _ => CreateDefaultShape(node)
    };

    CurrentLayer.Shapes.Add(nodeShape);
    AddVisualChild(nodeShape);
    }

    private void DrawConnection(NetworkConnection connection)
    {
    // 绘制连接线
    var line = new Line
    {
    X1 = connection.SourceNode.Position.X,
    Y1 = connection.SourceNode.Position.Y,
    X2 = connection.TargetNode.Position.X,
    Y2 = connection.TargetNode.Position.Y,
    Stroke = Brushes.Blue,
    StrokeThickness = 2,
    StrokeDashArray = connection.ConnectionType == "Wireless" ?
    new DoubleCollection { 4, 2 } : null
    };

    CurrentLayer.Shapes.Add(line);
    AddVisualChild(line);
    }
    }

    3.3.3 性能优化建议

  • 虚拟化渲染:对于大型绘图,只渲染可见区域的内容
  • 增量更新:只重绘发生变化的部分
  • 硬件加速:充分利用GPU进行渲染
  • 数据压缩:在网络传输时压缩绘图数据
  • 缓存机制:缓存常用图形和笔刷
  • 3.3.4 安全性考虑

  • 输入验证:防止恶意输入
  • 权限控制:基于角色的绘图权限管理
  • 数据加密:网络传输数据的加密
  • 审计日志:记录所有绘图操作
  • 防篡改:数字签名确保绘图完整性
  • 本章总结

    本章深入探讨了WPF中Ribbon控件和数字墨迹技术的实现与应用。通过从基础到高级的完整示例,展示了如何:

  • 构建专业界面:使用Ribbon控件创建功能丰富的网络工具界面
  • 实现自然绘图:利用InkCanvas和自定义墨迹控件提供自然的绘图体验
  • 优化网络传输:设计高效的墨迹数据传输方案
  • 创建协作应用:构建支持多用户协作的网络白板系统
  • 扩展专业功能:开发网络拓扑绘图等专业工具
  • 这些技术不仅可以应用于网络绘图工具,还可以扩展到远程教学系统、视频会议标注、工业设计协作等多个领域。通过结合C#网络编程知识,可以创建出功能强大、用户体验优秀的专业级应用程序。

    在实际开发中,应根据具体需求选择合适的技术方案,并充分考虑性能、安全和可扩展性等因素。随着技术的不断发展,这些基础技术可以与AI识别、云存储、实时协作等现代技术结合,创造出更加智能和强大的应用。

    赞(0)
    未经允许不得转载:171主机测评 » 第3章 WPF高级界面组件与数字墨迹绘图技术实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址