第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元素。
功能复用的设计模式:
网络工具功能复用示例:
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提供了完整的触笔事件处理机制,允许开发者创建响应灵敏的绘图应用。
触笔事件层次结构:
高级触笔事件处理示例:
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 网络协作白板的扩展功能
基于以上技术,可以构建功能完整的网络协作白板系统,具备以下扩展功能:
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 性能优化建议
3.3.4 安全性考虑
本章总结
本章深入探讨了WPF中Ribbon控件和数字墨迹技术的实现与应用。通过从基础到高级的完整示例,展示了如何:
这些技术不仅可以应用于网络绘图工具,还可以扩展到远程教学系统、视频会议标注、工业设计协作等多个领域。通过结合C#网络编程知识,可以创建出功能强大、用户体验优秀的专业级应用程序。
在实际开发中,应根据具体需求选择合适的技术方案,并充分考虑性能、安全和可扩展性等因素。随着技术的不断发展,这些基础技术可以与AI识别、云存储、实时协作等现代技术结合,创造出更加智能和强大的应用。


