feat - 添加WPFNodeDemo
parent
5955b36967
commit
ffb005fec6
@ -0,0 +1,3 @@
|
|||||||
|
<Solution>
|
||||||
|
<Project Path="WpfNodeTest/WpfNodeTest.csproj" />
|
||||||
|
</Solution>
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
<Application x:Class="WpfNodeTest.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:WpfNodeTest"
|
||||||
|
StartupUri="MainWindow.xaml">
|
||||||
|
<Application.Resources>
|
||||||
|
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"NodeTypes": [
|
||||||
|
{
|
||||||
|
"TypeName": "提升机",
|
||||||
|
"DefaultInputCount": 1,
|
||||||
|
"DefaultOutputCount": 1,
|
||||||
|
"Description": "垂直运输设备"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TypeName": "电梯",
|
||||||
|
"DefaultInputCount": 1,
|
||||||
|
"DefaultOutputCount": 1,
|
||||||
|
"Description": "楼层间运输"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TypeName": "工位",
|
||||||
|
"DefaultInputCount": 1,
|
||||||
|
"DefaultOutputCount": 0,
|
||||||
|
"Description": "作业工位"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TypeName": "物流接驳位",
|
||||||
|
"DefaultInputCount": 2,
|
||||||
|
"DefaultOutputCount": 2,
|
||||||
|
"Description": "物流中转点"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"TypeName": "库位",
|
||||||
|
"DefaultInputCount": 1,
|
||||||
|
"DefaultOutputCount": 0,
|
||||||
|
"Description": "存储位置"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,84 @@
|
|||||||
|
<Window x:Class="WpfNodeTest.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:WpfNodeTest"
|
||||||
|
xmlns:nodenetwork="clr-namespace:NodeNetwork.Views;assembly=NodeNetwork"
|
||||||
|
xmlns:views="clr-namespace:WpfNodeTest.Views"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="MainWindow" Height="720" Width="1280">
|
||||||
|
<!--基GRID-->
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="50"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<!--顶边功能区-->
|
||||||
|
<Grid Grid.Row="0" Background="#2C2C2C">
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="10,0">
|
||||||
|
<TextBlock Text="AGV接驳位编辑器" FontSize="16" FontWeight="Bold" Foreground="White" VerticalAlignment="Center" Margin="0,0,20,0"/>
|
||||||
|
<Button Content="加载" Command="{Binding LoadFileCommand}" Width="80" Height="30" Margin="5,0"/>
|
||||||
|
<Button Content="保存" Command="{Binding SaveFileCommand}" Width="80" Height="30" Margin="5,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<!--顶边功能区-->
|
||||||
|
<!--工作区-->
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="300"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<!--侧边栏-->
|
||||||
|
<Grid Grid.Column="0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="400"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<!--控件库-->
|
||||||
|
<Grid Grid.Row="0" Background="#F5F5F5">
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel Margin="10">
|
||||||
|
<TextBlock Text="节点类型(拖拽到工作区)" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
|
||||||
|
<ItemsControl ItemsSource="{Binding NodeTypeInfos}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<views:NodeTemplateControl />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
<!--控件库-->
|
||||||
|
<!--属性栏-->
|
||||||
|
<Grid Grid.Row="1" Background="#F5F5F5">
|
||||||
|
<StackPanel Margin="10" DataContext="{Binding SelectedNode}">
|
||||||
|
<TextBlock Text="节点属性" FontWeight="Bold" FontSize="14" Margin="0,0,0,10"/>
|
||||||
|
|
||||||
|
<TextBlock Text="节点名称:" Margin="0,5,0,2"/>
|
||||||
|
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Height="25"/>
|
||||||
|
|
||||||
|
<TextBlock Text="节点类型:" Margin="0,10,0,2"/>
|
||||||
|
<TextBox Text="{Binding NodeType, UpdateSourceTrigger=PropertyChanged}" Height="25"/>
|
||||||
|
|
||||||
|
<TextBlock Text="输入端口数:" Margin="0,10,0,2"/>
|
||||||
|
<TextBox Text="{Binding InputCount, UpdateSourceTrigger=PropertyChanged}" Height="25"/>
|
||||||
|
|
||||||
|
<TextBlock Text="输出端口数:" Margin="0,10,0,2"/>
|
||||||
|
<TextBox Text="{Binding OutputCount, UpdateSourceTrigger=PropertyChanged}" Height="25"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<!--属性栏-->
|
||||||
|
</Grid>
|
||||||
|
<!--侧边栏-->
|
||||||
|
<!--编辑器-->
|
||||||
|
<Grid Grid.Column="1">
|
||||||
|
<nodenetwork:NetworkView x:Name="networkView" ViewModel="{Binding Network}" AllowDrop="True" DragOver="NetworkView_DragOver" Drop="NetworkView_Drop" />
|
||||||
|
</Grid>
|
||||||
|
<!--编辑器-->
|
||||||
|
</Grid>
|
||||||
|
<!--工作区-->
|
||||||
|
</Grid>
|
||||||
|
<!--基GRID-->
|
||||||
|
</Window>
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using WpfNodeTest.Models;
|
||||||
|
using WpfNodeTest.ViewModels;
|
||||||
|
|
||||||
|
namespace WpfNodeTest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
DataContext = new MainWindowViewModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理拖拽悬停事件,允许拖放
|
||||||
|
private void NetworkView_DragOver(object sender, DragEventArgs e)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("DragOver事件触发");
|
||||||
|
if (e.Data.GetDataPresent(typeof(NodeTypeInfo)))
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("检测到NodeTypeInfo数据");
|
||||||
|
e.Effects = DragDropEffects.Copy;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("未检测到NodeTypeInfo数据");
|
||||||
|
e.Effects = DragDropEffects.None;
|
||||||
|
}
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理拖放事件,在工作区创建新节点
|
||||||
|
private void NetworkView_Drop(object sender, DragEventArgs e)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("Drop事件触发");
|
||||||
|
if (e.Data.GetData(typeof(NodeTypeInfo)) is NodeTypeInfo nodeType && DataContext is MainWindowViewModel vm)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"创建节点: {nodeType.TypeName}");
|
||||||
|
var dropPosition = e.GetPosition(networkView);
|
||||||
|
|
||||||
|
var node = new AGVDockingNode
|
||||||
|
{
|
||||||
|
Name = nodeType.TypeName,
|
||||||
|
NodeType = nodeType.TypeName,
|
||||||
|
InputCount = nodeType.DefaultInputCount,
|
||||||
|
OutputCount = nodeType.DefaultOutputCount,
|
||||||
|
Position = new Point(dropPosition.X, dropPosition.Y)
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.Network.Nodes.Edit(list => list.Add(node));
|
||||||
|
System.Diagnostics.Debug.WriteLine($"节点已添加到位置: ({dropPosition.X}, {dropPosition.Y})");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("Drop失败: 数据类型不匹配或ViewModel为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
using NodeNetwork.ViewModels;
|
||||||
|
using NodeNetwork.Views;
|
||||||
|
using ReactiveUI;
|
||||||
|
|
||||||
|
namespace WpfNodeTest.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// AGV接驳位节点模型
|
||||||
|
/// </summary>
|
||||||
|
public class AGVDockingNode : NodeViewModel
|
||||||
|
{
|
||||||
|
// 静态构造函数:注册节点视图
|
||||||
|
static AGVDockingNode()
|
||||||
|
{
|
||||||
|
Splat.Locator.CurrentMutable.Register(() => new NodeView(), typeof(IViewFor<AGVDockingNode>));
|
||||||
|
}
|
||||||
|
// 节点唯一标识
|
||||||
|
private string _nodeId = Guid.NewGuid().ToString();
|
||||||
|
public string NodeId
|
||||||
|
{
|
||||||
|
get => _nodeId;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _nodeId, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 节点类型(提升机、电梯、工位、物流接驳位、库位等)
|
||||||
|
private string _nodeType = "默认";
|
||||||
|
public string NodeType
|
||||||
|
{
|
||||||
|
get => _nodeType;
|
||||||
|
set => this.RaiseAndSetIfChanged(ref _nodeType, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输入端口数量
|
||||||
|
private int _inputCount = 1;
|
||||||
|
public int InputCount
|
||||||
|
{
|
||||||
|
get => _inputCount;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.RaiseAndSetIfChanged(ref _inputCount, value);
|
||||||
|
UpdatePorts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出端口数量
|
||||||
|
private int _outputCount = 1;
|
||||||
|
public int OutputCount
|
||||||
|
{
|
||||||
|
get => _outputCount;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.RaiseAndSetIfChanged(ref _outputCount, value);
|
||||||
|
UpdatePorts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AGVDockingNode()
|
||||||
|
{
|
||||||
|
Name = "接驳位";
|
||||||
|
UpdatePorts();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据输入输出数量更新端口
|
||||||
|
private void UpdatePorts()
|
||||||
|
{
|
||||||
|
Inputs.Edit(list =>
|
||||||
|
{
|
||||||
|
list.Clear();
|
||||||
|
for (int i = 0; i < InputCount; i++)
|
||||||
|
{
|
||||||
|
list.Add(new NodeInputViewModel
|
||||||
|
{
|
||||||
|
Name = $"入口{i + 1}",
|
||||||
|
MaxConnections = int.MaxValue // 允许多个连接
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Outputs.Edit(list =>
|
||||||
|
{
|
||||||
|
list.Clear();
|
||||||
|
for (int i = 0; i < OutputCount; i++)
|
||||||
|
{
|
||||||
|
list.Add(new NodeOutputViewModel
|
||||||
|
{
|
||||||
|
Name = $"出口{i + 1}",
|
||||||
|
MaxConnections = int.MaxValue // 允许多个连接
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
namespace WpfNodeTest.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点图数据模型,用于序列化和反序列化
|
||||||
|
/// </summary>
|
||||||
|
public class NodeGraphData
|
||||||
|
{
|
||||||
|
public List<NodeData> Nodes { get; set; } = new();
|
||||||
|
public List<ConnectionData> Connections { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点数据
|
||||||
|
/// </summary>
|
||||||
|
public class NodeData
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string Type { get; set; } = string.Empty;
|
||||||
|
public double X { get; set; }
|
||||||
|
public double Y { get; set; }
|
||||||
|
public int InputCount { get; set; }
|
||||||
|
public int OutputCount { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 连接数据
|
||||||
|
/// </summary>
|
||||||
|
public class ConnectionData
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = string.Empty;
|
||||||
|
public string SourceNodeId { get; set; } = string.Empty;
|
||||||
|
public int SourcePortIndex { get; set; }
|
||||||
|
public string TargetNodeId { get; set; } = string.Empty;
|
||||||
|
public int TargetPortIndex { get; set; }
|
||||||
|
public double Weight { get; set; } = 1.0; // 权重(耗时),用于路径规划
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
namespace WpfNodeTest.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点类型配置
|
||||||
|
/// </summary>
|
||||||
|
public class NodeTypeConfig
|
||||||
|
{
|
||||||
|
public List<NodeTypeInfo> NodeTypes { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点类型信息
|
||||||
|
/// </summary>
|
||||||
|
public class NodeTypeInfo
|
||||||
|
{
|
||||||
|
public string TypeName { get; set; } = string.Empty; // 类型名称
|
||||||
|
public int DefaultInputCount { get; set; } = 1; // 默认输入端口数
|
||||||
|
public int DefaultOutputCount { get; set; } = 1; // 默认输出端口数
|
||||||
|
public string Description { get; set; } = string.Empty; // 描述
|
||||||
|
}
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
using WpfNodeTest.Models;
|
||||||
|
|
||||||
|
namespace WpfNodeTest.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点类型配置服务
|
||||||
|
/// </summary>
|
||||||
|
public class NodeTypeConfigService
|
||||||
|
{
|
||||||
|
private NodeTypeConfig? _config;
|
||||||
|
private readonly string _configPath = "Config/NodeTypes.json";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加载节点类型配置
|
||||||
|
/// </summary>
|
||||||
|
public NodeTypeConfig LoadConfig()
|
||||||
|
{
|
||||||
|
if (_config != null) return _config;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!File.Exists(_configPath))
|
||||||
|
{
|
||||||
|
// 配置文件不存在,返回默认配置
|
||||||
|
_config = CreateDefaultConfig();
|
||||||
|
return _config;
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = File.ReadAllText(_configPath);
|
||||||
|
_config = JsonSerializer.Deserialize<NodeTypeConfig>(json) ?? CreateDefaultConfig();
|
||||||
|
return _config;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// 加载失败,返回默认配置
|
||||||
|
System.Diagnostics.Debug.WriteLine($"加载配置文件失败: {ex.Message}");
|
||||||
|
_config = CreateDefaultConfig();
|
||||||
|
return _config;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 创建默认配置
|
||||||
|
/// </summary>
|
||||||
|
private NodeTypeConfig CreateDefaultConfig()
|
||||||
|
{
|
||||||
|
return new NodeTypeConfig
|
||||||
|
{
|
||||||
|
NodeTypes = new List<NodeTypeInfo>
|
||||||
|
{
|
||||||
|
new NodeTypeInfo { TypeName = "提升机", DefaultInputCount = 1, DefaultOutputCount = 1, Description = "垂直运输设备" },
|
||||||
|
new NodeTypeInfo { TypeName = "电梯", DefaultInputCount = 1, DefaultOutputCount = 1, Description = "楼层间运输" },
|
||||||
|
new NodeTypeInfo { TypeName = "工位", DefaultInputCount = 1, DefaultOutputCount = 0, Description = "作业工位" },
|
||||||
|
new NodeTypeInfo { TypeName = "物流接驳位", DefaultInputCount = 1, DefaultOutputCount = 1, Description = "物流中转点" },
|
||||||
|
new NodeTypeInfo { TypeName = "库位", DefaultInputCount = 1, DefaultOutputCount = 0, Description = "存储位置" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取所有节点类型名称
|
||||||
|
/// </summary>
|
||||||
|
public List<string> GetNodeTypeNames()
|
||||||
|
{
|
||||||
|
var config = LoadConfig();
|
||||||
|
return config.NodeTypes.Select(t => t.TypeName).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据类型名称获取节点类型信息
|
||||||
|
/// </summary>
|
||||||
|
public NodeTypeInfo? GetNodeTypeInfo(string typeName)
|
||||||
|
{
|
||||||
|
var config = LoadConfig();
|
||||||
|
return config.NodeTypes.FirstOrDefault(t => t.TypeName == typeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using NodeNetwork.ViewModels;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using WpfNodeTest.Models;
|
||||||
|
using WpfNodeTest.Services;
|
||||||
|
|
||||||
|
namespace WpfNodeTest.ViewModels;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主窗口ViewModel
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindowViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly NodeGraphSerializer _serializer = new();
|
||||||
|
private readonly NodeTypeConfigService _configService = new();
|
||||||
|
private readonly PathPlanningService _pathPlanning = new();
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private NetworkViewModel _network = new();
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ObservableCollection<string> _nodeTypes = new();
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ObservableCollection<NodeTypeInfo> _nodeTypeInfos = new();
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private AGVDockingNode? _selectedNode;
|
||||||
|
|
||||||
|
public MainWindowViewModel()
|
||||||
|
{
|
||||||
|
LoadNodeTypes();
|
||||||
|
|
||||||
|
// 监听节点选择变化
|
||||||
|
Network.SelectedNodes.Connect().Subscribe(_ =>
|
||||||
|
{
|
||||||
|
SelectedNode = Network.SelectedNodes.Items.FirstOrDefault() as AGVDockingNode;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载节点类型列表
|
||||||
|
private void LoadNodeTypes()
|
||||||
|
{
|
||||||
|
var config = _configService.LoadConfig();
|
||||||
|
NodeTypes = new ObservableCollection<string>(config.NodeTypes.Select(t => t.TypeName));
|
||||||
|
NodeTypeInfos = new ObservableCollection<NodeTypeInfo>(config.NodeTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 加载节点图文件
|
||||||
|
/// </summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void LoadFile()
|
||||||
|
{
|
||||||
|
var dialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Filter = "JSON文件|*.json",
|
||||||
|
Title = "加载节点图"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
Network = _serializer.LoadFromFile(dialog.FileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 保存节点图文件
|
||||||
|
/// </summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void SaveFile()
|
||||||
|
{
|
||||||
|
var dialog = new SaveFileDialog
|
||||||
|
{
|
||||||
|
Filter = "JSON文件|*.json",
|
||||||
|
Title = "保存节点图"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
_serializer.SaveToFile(Network, dialog.FileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加新节点
|
||||||
|
/// </summary>
|
||||||
|
[RelayCommand]
|
||||||
|
private void AddNode(string nodeType)
|
||||||
|
{
|
||||||
|
var typeInfo = _configService.GetNodeTypeInfo(nodeType);
|
||||||
|
var node = new AGVDockingNode
|
||||||
|
{
|
||||||
|
Name = nodeType,
|
||||||
|
NodeType = nodeType,
|
||||||
|
InputCount = typeInfo?.DefaultInputCount ?? 1,
|
||||||
|
OutputCount = typeInfo?.DefaultOutputCount ?? 1,
|
||||||
|
Position = new System.Windows.Point(100, 100)
|
||||||
|
};
|
||||||
|
|
||||||
|
Network.Nodes.Edit(list => list.Add(node));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
<UserControl x:Class="WpfNodeTest.Views.NodeTemplateControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Width="250" Height="60" Margin="5">
|
||||||
|
<Border Background="#E0E0E0" BorderBrush="#999" BorderThickness="1" CornerRadius="5" Cursor="Hand">
|
||||||
|
<StackPanel Margin="10" VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="{Binding TypeName}" FontWeight="Bold" FontSize="12"/>
|
||||||
|
<TextBlock Text="{Binding Description}" FontSize="10" Foreground="#666" Margin="0,2,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using WpfNodeTest.Models;
|
||||||
|
|
||||||
|
namespace WpfNodeTest.Views
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 节点模板控件
|
||||||
|
/// </summary>
|
||||||
|
public partial class NodeTemplateControl : UserControl
|
||||||
|
{
|
||||||
|
private Point _startPoint;
|
||||||
|
private bool _isDragging;
|
||||||
|
|
||||||
|
public NodeTemplateControl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
PreviewMouseLeftButtonDown += OnPreviewMouseLeftButtonDown;
|
||||||
|
PreviewMouseMove += OnPreviewMouseMove;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录鼠标按下位置
|
||||||
|
private void OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
_startPoint = e.GetPosition(null);
|
||||||
|
_isDragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 鼠标移动时启动拖放
|
||||||
|
private void OnPreviewMouseMove(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed && !_isDragging)
|
||||||
|
{
|
||||||
|
Point currentPosition = e.GetPosition(null);
|
||||||
|
Vector diff = _startPoint - currentPosition;
|
||||||
|
|
||||||
|
// 移动超过阈值才启动拖拽
|
||||||
|
if (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
|
||||||
|
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
|
||||||
|
{
|
||||||
|
_isDragging = true;
|
||||||
|
if (DataContext is NodeTypeInfo nodeType)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"启动拖拽: {nodeType.TypeName}");
|
||||||
|
DragDrop.DoDragDrop(this, nodeType, DragDropEffects.Copy);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine("DataContext不是NodeTypeInfo类型");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"Nodes": [
|
||||||
|
{
|
||||||
|
"Id": "node-1",
|
||||||
|
"Name": "工位A",
|
||||||
|
"Type": "工位",
|
||||||
|
"X": 100,
|
||||||
|
"Y": 100,
|
||||||
|
"InputCount": 1,
|
||||||
|
"OutputCount": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "node-2",
|
||||||
|
"Name": "提升机B",
|
||||||
|
"Type": "提升机",
|
||||||
|
"X": 300,
|
||||||
|
"Y": 100,
|
||||||
|
"InputCount": 2,
|
||||||
|
"OutputCount": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "node-3",
|
||||||
|
"Name": "接驳位C",
|
||||||
|
"Type": "物流接驳位",
|
||||||
|
"X": 500,
|
||||||
|
"Y": 100,
|
||||||
|
"InputCount": 2,
|
||||||
|
"OutputCount": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "node-4",
|
||||||
|
"Name": "库位D",
|
||||||
|
"Type": "库位",
|
||||||
|
"X": 700,
|
||||||
|
"Y": 100,
|
||||||
|
"InputCount": 1,
|
||||||
|
"OutputCount": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Connections": [
|
||||||
|
{
|
||||||
|
"Id": "conn-1",
|
||||||
|
"SourceNodeId": "node-1",
|
||||||
|
"SourcePortIndex": 0,
|
||||||
|
"TargetNodeId": "node-2",
|
||||||
|
"TargetPortIndex": 0,
|
||||||
|
"Weight": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "conn-2",
|
||||||
|
"SourceNodeId": "node-2",
|
||||||
|
"SourcePortIndex": 0,
|
||||||
|
"TargetNodeId": "node-3",
|
||||||
|
"TargetPortIndex": 0,
|
||||||
|
"Weight": 1.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "conn-3",
|
||||||
|
"SourceNodeId": "node-3",
|
||||||
|
"SourcePortIndex": 0,
|
||||||
|
"TargetNodeId": "node-4",
|
||||||
|
"TargetPortIndex": 0,
|
||||||
|
"Weight": 1.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue