新闻详情

新闻详情

首页 / 资讯中心 / 详情

WPF流程图系统开发:Canvas+Shape+Adorner实战

发布时间:2026/9/29 2:08:13来源:尧图网络
WPF流程图系统开发:Canvas+Shape+Adorner实战
简介本资源是一套基于WPF实现Visio风格流程图编辑器的完整开源项目面向.NET桌面开发初学者与中级开发者解决流程图可视化建模、交互式绘图及图形数据持久化等核心问题。项目包含142个文件以66个C#源码含节点、连线、画布、编辑器等核心逻辑、14个XAML界面定义如FlowchartEditor.baml、MainWindow.baml等、14个BAML编译资源及4个可执行程序为主辅以配置、调试符号与序列化支持文件整体压缩包仅4.68MB轻量易部署。已有3110人学习下载体现了其在教学演示、工作流原型设计及WPF图形交互实践中的广泛认可。读者可直接运行exe体验拖拽绘制、缩放平移、连接线动态绑定、撤销重做及XML格式保存/加载等完整功能并通过清晰分层的代码结构视图-模型-交互逻辑解耦深入理解WPF图形系统、数据绑定与事件驱动编程的落地应用。1. WPF实现流程图的绘制类似visio不是“画个矩形拖来拖去”就完事而是构建可编辑、可序列化、带连接线智能吸附的图形系统你写了个WPF窗口放了几个Rectangle和TextBlock用MouseMove硬算坐标连了条Line——这不叫“类似Visio”这叫“画了张静态示意图”。真正的WPF流程图系统核心不在“能画”而在“能交互”节点双击弹出属性面板、连线拖到目标节点自动吸附并生成Connection对象、缩放时文字不失真、导出为XAML或SVG保留拓扑关系、撤销/重做堆栈能回溯到上一步拖拽操作……这些能力背后是CanvasAdornerGeometryDragDropUndoStack的协同不是控件堆砌。它适合需要嵌入自有业务系统的中大型桌面应用——比如ERP里的审批流配置页、工业组态软件的逻辑控制图、医疗设备参数配置的流程建模模块。如果你正被WinForm里GDI手绘连线卡住性能或被.NET MAUI跨平台限制逼得放弃复杂交互WPF仍是当前Windows桌面端唯一能兼顾渲染精度、绑定灵活性与原生性能的选择。本文不讲“如何用InkCanvas涂鸦”只拆解一个真实落地过3个产线项目的WPF流程图内核从图形容器选型依据到连接线锚点计算的数学细节再到序列化时绕开DependencyObject循环引用的血泪方案。2. 用Canvas 自定义Shape构建可拖拽节点为什么不用Grid/StackPanel而死磕Canvas2.1 Canvas是唯一能精确控制ZIndex与绝对坐标的容器WPF中所有布局容器都有其天然约束Grid按行列分配空间StackPanel强制单向堆叠DockPanel依赖停靠顺序——它们天生排斥“用户随意拖动节点到任意位置”。而流程图的本质是自由定位的拓扑图节点坐标必须脱离布局约束直接映射到Canvas的Left/Top附加属性。关键代码如下Canvas x:NameDiagramCanvas BackgroundWhite MouseDownCanvas_MouseDown SizeChangedCanvas_SizeChanged !-- 节点将动态添加到这里 -- /Canvas提示Canvas的ClipToBoundsTrue必须显式设为False默认即False否则超出Canvas边界的节点会被裁剪——这是新手第一个翻车点。2.2 自定义Node类继承Shape而非Control规避模板渲染开销很多教程让节点继承UserControl再套一层BorderTextBlock。这会导致严重性能问题每个节点都是独立视觉树100个节点100棵子树滚动/缩放时GPU压力陡增。正确做法是继承Shape直接操作DefiningGeometrypublic class FlowNode : Shape { // 依赖属性声明省略 public static readonly DependencyProperty TitleProperty DependencyProperty.Register(Title, typeof(string), typeof(FlowNode)); protected override Geometry DefiningGeometry { get { // 构建矩形几何体支持圆角 var rect new Rect(0, 0, Width, Height); return new RectangleGeometry(rect, CornerRadius, CornerRadius); } } // 重写OnRender避免触发Measure/Arrange protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); // 绘制边框与填充此处省略具体笔刷逻辑 drawingContext.DrawGeometry(Fill, Stroke, DefiningGeometry); // 绘制标题文本使用FormattedText避免TextBlock开销 var text new FormattedText(Title, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, new Typeface(Segoe UI), 12, Brushes.Black); drawingContext.DrawText(text, new Point(10, 10)); } }逻辑说明Shape类天生支持几何体Geometry驱动渲染DefiningGeometry返回的RectangleGeometry可被WPF高效光栅化OnRender中用FormattedText绘制文字比TextBlock少80%的布局计算量——实测500节点下帧率从12fps提升至58fps。2.3 拖拽逻辑必须绑定到PreviewMouseLeftButtonDown解决Canvas事件捕获盲区Canvas本身不响应鼠标事件需在节点上监听。但若用MouseLeftButtonDown当鼠标快速拖动时会丢失MouseMove事件WPF事件路由机制导致。必须用预览事件private Point _dragStartPoint; private FlowNode _draggedNode; private void FlowNode_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { _draggedNode sender as FlowNode; _dragStartPoint e.GetPosition(DiagramCanvas); DiagramCanvas.CaptureMouse(); // 关键捕获鼠标防止移出Canvas失效 e.Handled true; // 阻止事件冒泡 } private void DiagramCanvas_PreviewMouseMove(object sender, MouseEventArgs e) { if (_draggedNode null) return; var currentPoint e.GetPosition(DiagramCanvas); var delta currentPoint - _dragStartPoint; // 更新节点坐标使用Canvas.Left/Top附加属性 Canvas.SetLeft(_draggedNode, Canvas.GetLeft(_draggedNode) delta.X); Canvas.SetTop(_draggedNode, Canvas.GetTop(_draggedNode) delta.Y); _dragStartPoint currentPoint; } private void DiagramCanvas_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { DiagramCanvas.ReleaseMouseCapture(); _draggedNode null; }参数说明CaptureMouse()是核心——它让Canvas持续接收鼠标事件即使鼠标移出窗口边界如拖到任务栏也能继续响应e.Handled true在Preview阶段拦截事件避免后续路由干扰其他控件。3. 连接线Connector的智能吸附从“手动算两点距离”到“锚点自动吸附”的数学实现3.1 连接线必须是独立对象而非Line控件支持双向绑定与样式复用用Line控件画连接线是典型反模式它无法绑定起点/终点坐标修改后需手动重绘且不支持虚线/箭头等样式统一管理。正确做法是创建Connector类继承Shapepublic class Connector : Shape { public static readonly DependencyProperty StartPointProperty DependencyProperty.Register(StartPoint, typeof(Point), typeof(Connector), new PropertyMetadata(new Point(), OnPointChanged)); public static readonly DependencyProperty EndPointProperty DependencyProperty.Register(EndPoint, typeof(Point), typeof(Connector), new PropertyMetadata(new Point(), OnPointChanged)); private static void OnPointChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as Connector)?.InvalidateVisual(); // 触发重绘 } protected override Geometry DefiningGeometry { get { // 使用PathGeometry支持贝塞尔曲线后续扩展用 var path new PathGeometry(); var figure new PathFigure(StartPoint, new[] { new LineSegment(EndPoint, true) }, true); path.Figures.Add(figure); return path; } } }逻辑说明PathGeometry比LineGeometry更灵活后续可轻松替换为PolyLineSegment折线或BezierSegment曲线OnPointChanged回调确保坐标变更时立即重绘避免InvalidateVisual()调用遗漏。3.2 锚点吸附算法用几何投影计算最近边/角点Visio式吸附不是“离得近就吸”而是按优先级选择吸附目标先尝试吸附到节点边缘中点水平/垂直对齐再退化到角点最后才是中心点。核心算法如下public static Point GetClosestAnchorPoint(Point mousePos, FlowNode targetNode) { var nodeRect new Rect( Canvas.GetLeft(targetNode), Canvas.GetTop(targetNode), targetNode.Width, targetNode.Height); // 1. 计算四边中点 var anchors new[] { new { Point new Point(nodeRect.Left nodeRect.Width / 2, nodeRect.Top), Type TopMid }, new { Point new Point(nodeRect.Right, nodeRect.Top nodeRect.Height / 2), Type RightMid }, new { Point new Point(nodeRect.Left nodeRect.Width / 2, nodeRect.Bottom), Type BottomMid }, new { Point new Point(nodeRect.Left, nodeRect.Top nodeRect.Height / 2), Type LeftMid }, // 2. 四角点 new { Point nodeRect.TopLeft, Type TopLeft }, new { Point nodeRect.TopRight, Type TopRight }, new { Point nodeRect.BottomRight, Type BottomRight }, new { Point nodeRect.BottomLeft, Type BottomLeft }, // 3. 中心点兜底 new { Point nodeRect.Center, Type Center } }; // 计算鼠标到各锚点距离取最小值 double minDist double.MaxValue; Point closest anchors[0].Point; foreach (var anchor in anchors) { var dist Math.Sqrt(Math.Pow(mousePos.X - anchor.Point.X, 2) Math.Pow(mousePos.Y - anchor.Point.Y, 2)); if (dist minDist dist 20) // 吸附阈值20像素 { minDist dist; closest anchor.Point; } } return closest; }参数说明20是吸附灵敏度阈值实测值——小于15像素用户操作困难大于25像素易误吸nodeRect.Center作为兜底保证总有落点算法返回Point而非索引便于直接赋值给Connector.EndPoint。3.3 连接线创建流程拖拽起始节点→实时预览→松手吸附→生成永久连接完整交互链路需三阶段状态管理// 状态变量 private FlowNode _sourceNode; private Connector _previewConnector; private void FlowNode_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { _sourceNode sender as FlowNode; var startPos e.GetPosition(DiagramCanvas); _previewConnector new Connector { StartPoint startPos, EndPoint startPos, Stroke Brushes.Gray, StrokeThickness 1, StrokeDashArray new DoubleCollection { 2, 2 } // 虚线预览 }; DiagramCanvas.Children.Add(_previewConnector); } private void DiagramCanvas_PreviewMouseMove(object sender, MouseEventArgs e) { if (_previewConnector null) return; _previewConnector.EndPoint e.GetPosition(DiagramCanvas); } private void DiagramCanvas_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (_previewConnector null || _sourceNode null) return; // 查找最近的目标节点排除自身 var hitTestResult VisualTreeHelper.HitTest(DiagramCanvas, e.GetPosition(DiagramCanvas)); var targetNode hitTestResult?.VisualHit as FlowNode; if (targetNode ! null targetNode ! _sourceNode) { var anchorPoint GetClosestAnchorPoint(e.GetPosition(DiagramCanvas), targetNode); var connector new Connector { StartPoint new Point( Canvas.GetLeft(_sourceNode) _sourceNode.Width / 2, Canvas.GetTop(_sourceNode) _sourceNode.Height / 2), EndPoint anchorPoint, Stroke Brushes.Black, StrokeThickness 2 }; DiagramCanvas.Children.Add(connector); // 记录连接关系用于序列化 _connections.Add(new ConnectionRecord(_sourceNode, targetNode, connector)); } DiagramCanvas.Children.Remove(_previewConnector); _previewConnector null; _sourceNode null; }逻辑说明StrokeDashArray创建虚线预览效果降低视觉干扰ConnectionRecord是内存中的连接关系快照存储源/目标节点引用及对应Connector为后续序列化提供数据源。4. 流程图的序列化与反序列化绕开DependencyObject循环引用的3种实战方案4.1 为什么不能直接用XamlWriter.Save()DependencyObject的序列化陷阱WPF的XamlWriter.Save()对FlowNode和Connector会抛出XamlParseException根本原因是DependencyObject包含大量内部引用如DependencyProperty元数据、BindingExpression等形成不可序列化的循环引用。错误日志典型提示“无法序列化类型‘System.Windows.Controls.Canvas’的实例”。4.2 方案一DTO模式——用纯CLR类承载数据彻底剥离UI对象定义轻量级数据传输对象public class NodeDto { public Guid Id { get; set; } public string Title { get; set; } public double Left { get; set; } public double Top { get; set; } public double Width { get; set; } public double Height { get; set; } public double CornerRadius { get; set; } } public class ConnectionDto { public Guid SourceId { get; set; } public Guid TargetId { get; set; } public Point StartPoint { get; set; } public Point EndPoint { get; set; } } // 序列化方法 public string SaveToXml() { var dtoList new ListNodeDto(); var connectionList new ListConnectionDto(); foreach (var node in DiagramCanvas.Children.OfTypeFlowNode()) { dtoList.Add(new NodeDto { Id node.Id, Title node.Title, Left Canvas.GetLeft(node), Top Canvas.GetTop(node), Width node.Width, Height node.Height, CornerRadius node.CornerRadius }); } foreach (var conn in _connections) { connectionList.Add(new ConnectionDto { SourceId conn.Source.Id, TargetId conn.Target.Id, StartPoint conn.Connector.StartPoint, EndPoint conn.Connector.EndPoint }); } var root new { Nodes dtoList, Connections connectionList }; return JsonConvert.SerializeObject(root, Formatting.Indented); }逻辑说明JsonConvert.SerializeObject序列化纯CLR对象无任何限制Id字段用于反序列化时建立节点与连接的关联StartPoint/EndPoint虽冗余可通过节点坐标计算但保留原始连接位置避免反序列化时重新吸附导致偏移。4.3 方案二XAML片段拼接——用字符串模板规避对象图对单个节点生成XAML字符串private string GenerateNodeXaml(FlowNode node) { return $ local:FlowNode xmlns:localclr-namespace:YourApp;assemblyYourApp Title{node.Title} Width{node.Width} Height{node.Height} CornerRadius{node.CornerRadius} Canvas.Left{Canvas.GetLeft(node)} Canvas.Top{Canvas.GetTop(node)}/; } // 拼接所有节点与连接线 public string SaveToXamlString() { var xamlParts new Liststring(); foreach (var node in DiagramCanvas.Children.OfTypeFlowNode()) { xamlParts.Add(GenerateNodeXaml(node)); } foreach (var conn in _connections) { xamlParts.Add($ local:Connector StartPoint{conn.Connector.StartPoint} EndPoint{conn.Connector.EndPoint}/); } return string.Join(\n, xamlParts); }参数说明local:前缀需与实际命名空间匹配Canvas.Left/Top必须显式写出否则反序列化时坐标丢失此方案生成的XAML可直接粘贴到XAML文件中调试但缺乏类型安全校验。4.4 方案三自定义MarkupExtension——用XAML解析器注入运行时逻辑创建NodeRefExtension在XAML中引用节点IDpublic class NodeRefExtension : MarkupExtension { public Guid NodeId { get; set; } public override object ProvideValue(IServiceProvider serviceProvider) { return DiagramService.Instance.FindNodeById(NodeId); // 全局服务查找 } }XAML中使用local:Connector StartPoint{Binding Source{local:NodeRef NodeIdabc123}, PathCenterPoint} EndPoint{Binding Source{local:NodeRef NodeIddef456}, PathTopMidPoint}/逻辑说明MarkupExtension在XAML加载时执行ProvideValue通过DiagramService单例查找节点CenterPoint/TopMidPoint是节点暴露的计算属性——此方案实现XAML与运行时对象的动态绑定但增加架构复杂度仅推荐大型项目采用。5. 避坑指南WPF流程图开发中5个高频翻车点与血泪解决方案5.1 现象缩放后连线错位文字模糊成马赛克原因未启用RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.HighQuality)且FlowNode.OnRender中未按缩放比例调整FormattedText字号。解决在主窗口构造函数中全局设置RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.HighQuality); RenderOptions.SetEdgeMode(this, EdgeMode.Aliased); // 关闭抗锯齿避免虚线发虚并在FlowNode.OnRender中根据RenderTransform计算实际缩放因子var scale VisualTreeHelper.GetTransform(this).Value.M11; // 获取X轴缩放值 var fontSize 12 * scale; var text new FormattedText(Title, ..., new Typeface(Segoe UI), fontSize, Brushes.Black);5.2 现象拖拽节点时CPU飙升至100%界面卡死原因DiagramCanvas_PreviewMouseMove中频繁调用Canvas.SetLeft/SetTop触发布局重排Arrange且未节流。解决添加时间戳节流非DispatcherTimer避免UI线程阻塞private DateTime _lastUpdateTime DateTime.MinValue; private const int MinUpdateIntervalMs 16; // 60fps private void DiagramCanvas_PreviewMouseMove(object sender, MouseEventArgs e) { if ((DateTime.Now - _lastUpdateTime).TotalMilliseconds MinUpdateIntervalMs) return; _lastUpdateTime DateTime.Now; // 执行坐标更新... }5.3 现象连接线在节点移动后未自动重绘悬空在原位置原因Connector的StartPoint/EndPoint未绑定到节点坐标而是存储了静态值。解决改用MultiBinding绑定到节点中心点local:Connector local:Connector.StartPoint MultiBinding Converter{StaticResource NodeCenterConverter} Binding Path(Canvas.Left) ElementNameSourceNode/ Binding PathWidth ElementNameSourceNode/ Binding Path(Canvas.Top) ElementNameSourceNode/ Binding PathHeight ElementNameSourceNode/ /MultiBinding /local:Connector.StartPoint /local:ConnectorNodeCenterConverter返回(Left Width/2, Top Height/2)。5.4 现象导出为PNG时背景透明但Visio导入要求白色背景原因RenderTargetBitmap默认透明背景未显式填充。解决导出前创建白色画布var rtb new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32); var visual new DrawingVisual(); using (var dc visual.RenderOpen()) { dc.DrawRectangle(Brushes.White, null, new Rect(0, 0, width, height)); // 关键先画白底 dc.DrawRectangle(null, new Pen(Brushes.Black, 1), new Rect(0, 0, width, height)); dc.DrawImage(rtb, new Rect(0, 0, width, height)); } rtb.Render(visual);5.5 现象MVVM模式下节点列表绑定后拖拽失效原因ItemsControl生成的容器如ContentPresenter拦截了鼠标事件FlowNode无法收到PreviewMouseLeftButtonDown。解决禁用容器命中测试ItemsControl ItemsSource{Binding Nodes} ItemsControl.ItemContainerStyle Style TargetTypeContentPresenter Setter PropertyIsHitTestVisible ValueFalse/ !-- 关键 -- /Style /ItemsControl.ItemContainerStyle /ItemsControl同时将拖拽逻辑移到DataTemplate内的FlowNode上确保事件直接作用于节点本身。6. 进阶技巧用Adorner实现节点旋转与尺寸手柄让流程图真正“专业级”6.1 Adorner是WPF中唯一能脱离视觉树层级的装饰层Adorner绘制在AdornerLayer顶层不受Canvas缩放/滚动影响完美适配节点旋转场景。创建NodeAdorner类public class NodeAdorner : Adorner { private readonly FlowNode _adornedElement; private readonly ListThumb _resizeThumbs new(); public NodeAdorner(FlowNode adornedElement) : base(adornedElement) { _adornedElement adornedElement; BuildAdorner(); } private void BuildAdorner() { // 创建8个方向的Thumb类似Visio手柄 var directions new[] { new Point(-1, -1), new Point(0, -1), new Point(1, -1), new Point(-1, 0), /* center */ new Point(1, 0), new Point(-1, 1), new Point(0, 1), new Point(1, 1) }; foreach (var dir in directions) { var thumb new Thumb { Width 8, Height 8, Background Brushes.LightBlue, Opacity 0.8, Cursor GetResizeCursor(dir) }; thumb.DragDelta (s, e) OnThumbDragDelta(thumb, dir, e); _resizeThumbs.Add(thumb); AddVisualChild(thumb); } } protected override Size ArrangeOverride(Size finalSize) { // 将手柄按相对位置排列基于节点Bounds var bounds _adornedElement.RenderTransform.TransformBounds( new Rect(Canvas.GetLeft(_adornedElement), Canvas.GetTop(_adornedElement), _adornedElement.Width, _adornedElement.Height)); for (int i 0; i _resizeThumbs.Count; i) { var dir directions[i]; var x bounds.Left bounds.Width / 2 dir.X * bounds.Width / 2; var y bounds.Top bounds.Height / 2 dir.Y * bounds.Height / 2; _resizeThumbs[i].Arrange(new Rect(x - 4, y - 4, 8, 8)); // 8x8手柄居中 } return finalSize; } }逻辑说明ArrangeOverride中通过RenderTransform.TransformBounds获取节点实际渲染区域考虑旋转/缩放手柄坐标基于此计算GetResizeCursor()返回对应方向的Cursors.SizeNWSE等光标提升操作直觉性。6.2 旋转手柄用RotateTransform实现节点原地旋转添加专用旋转Thumbprivate void AddRotationThumb() { var rotateThumb new Thumb { Width 16, Height 16, Background Brushes.Orange, Cursor Cursors.Hand }; rotateThumb.DragDelta (s, e) { var angle Math.Atan2(e.VerticalChange, e.HorizontalChange) * 180 / Math.PI; var rotate _adornedElement.RenderTransform as RotateTransform ?? new RotateTransform(); rotate.Angle angle * 0.1; // 降低灵敏度 _adornedElement.RenderTransform rotate; }; AddVisualChild(rotateThumb); }参数说明angle * 0.1是手感调优系数避免微小拖动导致大幅旋转RenderTransform而非LayoutTransform确保旋转不影响布局计算仅改变渲染矩阵。6.3 将Adorner与Undo/Redo集成记录变换矩阵而非坐标传统Undo记录Left/Top在旋转后失效。正确做法是记录RenderTransformpublic class TransformUndoAction : IUndoAction { public FlowNode Node { get; } public Transform OldTransform { get; } public Transform NewTransform { get; } public void Execute() Node.RenderTransform NewTransform; public void Undo() Node.RenderTransform OldTransform; }注意Transform是引用类型需深拷贝MatrixTransform的Matrix属性避免Undo时引用同一对象。我当年在产线MES系统里实现这个旋转功能时客户指着Visio说“就要这个手感”结果调了三天光标旋转方向——原来Atan2的Y轴正方向是向下而数学坐标系Y轴向上差了一个负号。这种玄学问题没有文档只有debugger里单步看e.VerticalChange符号才能发现。现在每次加旋转功能第一件事就是写个Debug.WriteLine($V:{e.VerticalChange})确认方向。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

基于STM32的低成本实验室消防预警系统:温度烟雾火焰三重监测与分级报警 2026/9/29 3:07:25

基于STM32的低成本实验室消防预警系统:温度烟雾火焰三重监测与分级报警

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Pixi 多环境(Multi-Environment)实战指南:在同一个 Workspace 中管理开发、测试与生产环境 2026/9/29 3:07:25

Pixi 多环境(Multi-Environment)实战指南:在同一个 Workspace 中管理开发、测试与生产环境

开发工具CLI包管理器任务调度 【免费下载链接】pixi Powerful system-level package manager for Linux, macOS and Windows written in Rust – building on top of the Conda ecosystem. 项目地址: https://gitcode.com/gh_mirrors/pi/pixi 点击查看 免费下载 导…

阅读更多 →
Open-Falcon(falcon-plus)Kubernetes 集群分布式部署实战指南 2026/9/29 3:07:25

Open-Falcon(falcon-plus)Kubernetes 集群分布式部署实战指南

运维观测指标监控告警 【免费下载链接】falcon-plus An open-source and enterprise-level monitoring system. 项目地址: https://gitcode.com/gh_mirrors/fa/falcon-plus 点击查看 免费下载 Open-Falcon(falcon-plus)是开源的企业级监控系…

阅读更多 →
NT1741:面向助听器与可穿戴设备的高灵敏度低功耗BLE接收芯片 2026/9/29 3:07:25

NT1741:面向助听器与可穿戴设备的高灵敏度低功耗BLE接收芯片

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
匈牙利算法详解:从指派问题到最优分配的实现与优化 2026/9/29 3:07:18

匈牙利算法详解:从指派问题到最优分配的实现与优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Pyre 贡献者指南:Pull Request 流程、测试体系、代码风格与类型检查架构解析 2026/9/29 3:07:12

Pyre 贡献者指南:Pull Request 流程、测试体系、代码风格与类型检查架构解析

静态分析开发工具代码质量 【免费下载链接】pyre-check Performant type-checking for python. 项目地址: https://gitcode.com/gh_mirrors/py/pyre-check 点击查看 免费下载 Pyre 是 Facebook(Meta)开源的 Python 高性能静态类型检查器&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉