前言
軟件開發領域,流程設計與可視化是提升系統可維護性、增強用户體驗的重要手段。無論是工作流管理、業務邏輯編排還是算法流程展示,一個靈活、易用的流程節點編輯框架都能極大地提高開發效率與系統靈活性。
本文將推薦一款基於 WPF 的開源流程節點編輯框架,通過對其核心設計與實現邏輯的解析,帶領大家從零開始手寫一個具備基礎功能的 WPF 流程圖編輯器,為實際項目中的可視化流程開發提供有價值的參考。
項目介紹
一款基於WPF的圖形化流程節點編輯工具,為大家提供一個直觀、高效的流程設計與編輯環境。通過拖拽節點、連接線等操作,可以輕鬆開發複雜的流程圖,實現業務邏輯的可視化表達。
項目功能
1、節點創建與編輯
支持多種類型的節點創建,包括但不限於開始節點、結束節點、處理節點、決策節點等。
通過簡單的點擊或拖拽操作,在畫布上添加、刪除或修改節點屬性,如節點名稱、顏色、形狀等。
2、連線管理
節點間通過連線表示流程走向,提供靈活的連線創建與編輯功能。
輕鬆地在節點間繪製直線、曲線或折線,調整連線的起點與終點,甚至設置連線的條件表達式,實現條件分支。
3、佈局調整與邊界擴展
滿足不同場景下的展示需求,支持畫布的縮放、平移以及節點的自動佈局功能。
根據需要調整畫布大小,通過手勢或按鈕控制畫布的顯示範圍。同時,框架還提供了邊界擴展功能,當節點靠近畫布邊緣時,自動擴展畫布大小,確保所有節點都能完整顯示。
4、框選與拖動
支持框選多個節點進行批量操作,如移動、刪除等。可以通過鼠標拖拽選擇框,選中多個節點後進行統一操作。同時,框架還提供節點的拖動功能,用户可以拖動單個或多個節點到指定位置。
5、數據綁定與交互
支持與後端數據的綁定,將流程圖中的節點屬性與數據庫字段或API接口關聯,實現數據的動態展示與交互。
另外,框架還提供了事件處理機制,用户可以自定義節點點擊、連線雙擊等事件的處理邏輯,增強流程圖的交互性。
項目特點
1、界面友好
WPF的豐富UI控件與動畫效果,提供直觀、美觀的操作界面。無論是節點的拖拽、連線的繪製還是屬性的編輯,都能帶來流暢的操作體驗。
2、擴展性強
設計遵循模塊化原則,各個功能模塊相對獨立,便於開發者根據需求進行二次開發或功能擴展。同時,框架提供豐富的API接口,方便與其他系統進行集成。
3、交互豐富
支持多種交互方式,如框選、拖動、右鍵菜單等,可以根據需要選擇合適的交互方式,提高操作效率。
項目技術
1、MVVM設計模式
為了實現界面與邏輯的分離,提高代碼的可維護性與可測試性,框架採用MVVM(Model-View-ViewModel)設計模式。通過數據綁定與命令機制,實現了視圖與模型之間的鬆耦合。
2、自定義控件開發
針對流程節點編輯的特殊需求,開發一系列自定義控件,如節點控件、連線控件等。控件通過繼承WPF的基礎控件類,實現了特定的功能與行為。
4、事件處理與委託
通過事件處理與委託機制,實現用户交互的響應與處理。無論是節點的點擊、連線的雙擊還是畫布的縮放,都能通過事件處理函數實現相應的邏輯。
項目代碼
框選拖動
/// <summary>
/// 添加連線更新方法
/// </summary>
/// <param name="selectedControls">選中的控件</param>
/// <param name="Xoffset">連線位置X軸的偏移量</param>
/// <param name="Yoffset">連線位置Y軸的偏移量</param>
private void UpdateConnectionsForNode(List<Control> selectedControls, double Xoffset = 0, double Yoffset = 0)
{
// 當前不存在連線的話直接結束
if (connections.Count == 0) return;
foreach (XNode node in selectedControls)
{
List<Connection> refConns = connections.FindAll(conn => IsChildOf(conn.FromPort, node) || IsChildOf(conn.ToPort, node));
refConns.ForEach(conn =>
{
Point p1 = PointAdd(GetPortCenter(conn.FromPort), new Point(Xoffset, Yoffset));
Point p2 = PointAdd(GetPortCenter(conn.ToPort), new Point(Xoffset, Yoffset));
var geometry = new PathGeometry();
var figure = new PathFigure { StartPoint = p1 };
var segment = CreateSegment("polyline", p1, p2);
figure.Segments.Add(segment);
geometry.Figures.Add(figure);
conn.Path.Data = geometry;
});
}
}
// 點位相加
private Point PointAdd(Point p1, Point p2)
{
return new Point(p1.X + p2.X, p1.Y + p2.Y);
}
private bool IsChildOf(FrameworkElement child, FrameworkElement parent)
{
var current = child;
while (current != null)
{
if (current == parent)
return true;
current = VisualTreeHelper.GetParent(current) as FrameworkElement;
}
return false;
}
線條拖動 | 線條類型切換
private void OutputPort_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (sender is FrameworkElement outputPort)
{
fromPort = outputPort;
startPoint = GetPortCenter(outputPort);
currentPath = new System.Windows.Shapes.Path
{
Stroke = Brushes.MediumPurple,
StrokeThickness = lineThickness,
Data = new PathGeometry()
};
currentPath.MouseLeftButtonDown += Path_MouseLeftButtonDown;
currentPath.MouseEnter += Path_MouseEnter;
currentPath.MouseLeave += Path_MouseLeave;
MainCanvas.Children.Add(currentPath);
isConnecting = true;
e.Handled = true;
}
}
private Point GetPortCenter(FrameworkElement port)
{
var point = new Point(port.Width / 2, port.Height / 2);
// 將當前點相對於port的座標轉換為當前點相對於Canvas的座標位置,Canvas會先獲取point左上角的位置,然後再偏移point.X,point.Y
var position = port.TranslatePoint(point, MainCanvas);
return position;
}
private PathSegment CreateSegment(string type, Point startPoint, Point endPoint)
{
if (string.IsNullOrEmpty(type))
throw new Exception("type 類型不能為空");
PathSegment segment;
if (type == "polyline")
{
if (startPoint.X <= endPoint.X - 40) // 兩邊距離大於40
{
double centerX = (startPoint.X + endPoint.X) / 2;
var polyline = new PolyLineSegment
{
Points = new PointCollection()
{
new Point(centerX,startPoint.Y),
new Point(centerX,endPoint.Y),
new Point(endPoint.X,endPoint.Y) // 終點
}
};
segment = polyline;
}
else
{
double centerY = (startPoint.Y + endPoint.Y) / 2;
var polyline = new PolyLineSegment
{
Points = new PointCollection()
{
new Point(startPoint.X + 20,startPoint.Y),
new Point(startPoint.X + 20,centerY),
new Point(endPoint.X - 20,centerY),
new Point(endPoint.X - 20,endPoint.Y),
new Point(endPoint.X,endPoint.Y) // 終點
}
};
segment = polyline;
}
}
else
{
var bezier = new BezierSegment
{
Point1 = new Point(startPoint.X + 50, startPoint.Y),
Point2 = new Point(endPoint.X - 50, endPoint.Y),
Point3 = endPoint
};
segment = bezier;
}
return segment;
}
項目效果
框架極大地簡化複雜流程的設計與實現過程,為項目的快速迭代與交付提供有力支持。
項目源碼
源碼結構清晰,註釋詳細,為大家提供良好的學習與參考價值。
代碼中包含節點的創建、連線的繪製、佈局的調整、框選與拖動的實現等核心功能,是學習WPF開發與流程節點編輯的好示例。
Gitee:https://gitee.com/Zero_0002/process-node-editing-framework
總結
流程節點編輯框架是一款功能強大、易於使用的圖形化流程設計工具。通過其豐富的功能特性、友好的操作界面以及強大的技術,框架在軟件開發領域展現出了廣泛的用途。
關鍵詞
WPF、流程節點編輯、連線管理、框選拖動、邊界擴展、數據綁定、MVVM模式、自定義控件、項目源碼、交互豐富流程圖、節點編輯、可視化、C#、開源項目、JSON、MVVM、拖拽、連線
最後
如果你覺得這篇文章對你有幫助,不妨點個贊支持一下!你的支持是我繼續分享知識的動力。如果有任何疑問或需要進一步的幫助,歡迎隨時留言。
也可以加入微信公眾號[DotNet技術匠] 社區,與其他熱愛技術的同行一起交流心得,共同成長!