diff --git a/Library/FlowNode/MethodDetails.cs b/Library/FlowNode/MethodDetails.cs index 8293ecb..ee07104 100644 --- a/Library/FlowNode/MethodDetails.cs +++ b/Library/FlowNode/MethodDetails.cs @@ -216,8 +216,8 @@ namespace Serein.Library MethodName = this.MethodName, MethodAnotherName = this.MethodAnotherName, NodeType = this.MethodDynamicType.ToString(), - ParameterDetailsInfos = this.ParameterDetailss.Select(p => p.ToInfo()).ToArray(), - ReturnTypeFullName = this.ReturnType.FullName, + ParameterDetailsInfos = this.ParameterDetailss?.Select(p => p.ToInfo()).ToArray(), + ReturnTypeFullName = this.ReturnType?.FullName, IsParamsArgIndex = this.ParamsArgIndex, }; } diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs index 7577c5f..c6e1f37 100644 --- a/Library/FlowNode/ParameterDetails.cs +++ b/Library/FlowNode/ParameterDetails.cs @@ -174,9 +174,9 @@ namespace Serein.Library IsParams = this.IsParams, DataTypeFullName = this.DataType.FullName, Name = this.Name, - ExplicitTypeFullName = this.ExplicitType.FullName, + ExplicitTypeFullName = this.ExplicitType?.FullName, InputType = this.InputType.ToString(), - Items = this.Items.Select(it => it).ToArray(), + Items = this.Items?.Select(it => it).ToArray(), }; } diff --git a/Library/Utils/ConvertHelper.cs b/Library/Utils/ConvertHelper.cs index 4fc56f2..ba9b238 100644 --- a/Library/Utils/ConvertHelper.cs +++ b/Library/Utils/ConvertHelper.cs @@ -200,6 +200,10 @@ namespace Serein.Library.Utils /// public static T ValueParse(object value) where T : struct, IComparable { + if (value is T data) + { + return data; + } string valueStr = value.ToString(); return valueStr.ToValueData() ; } @@ -228,7 +232,8 @@ namespace Serein.Library.Utils { if (string.IsNullOrEmpty(valueStr)) { - return default(T); + throw new NullReferenceException(); + //return default(T); } var type = typeof(T); object result; diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 0a59a2d..30d5fe8 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -1048,7 +1048,9 @@ namespace Serein.NodeFlow.Env return Task.FromResult(null); } NodeModelBase? nodeModel; - if (methodDetailsInfo is null) + if (methodDetailsInfo is null + || string.IsNullOrEmpty(methodDetailsInfo.AssemblyName) + || string.IsNullOrEmpty(methodDetailsInfo.MethodName)) { nodeModel = FlowNodeExtension.CreateNode(this, nodeControlType); // 加载基础节点 } diff --git a/NodeFlow/Model/SingleScriptNode.cs b/NodeFlow/Model/SingleScriptNode.cs index 3912158..3a53cea 100644 --- a/NodeFlow/Model/SingleScriptNode.cs +++ b/NodeFlow/Model/SingleScriptNode.cs @@ -12,6 +12,7 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace Serein.NodeFlow.Model { @@ -246,12 +247,29 @@ namespace Serein.NodeFlow.Model { public static DateTime GetNow() => DateTime.Now; + + #region 常用的类型转换 + public static bool BoolOf(object value) + { + return ConvertHelper.ValueParse(value); + } + public static int IntOf(object value) + { + return ConvertHelper.ValueParse(value); + } + public static int LongOf(object value) + { + return ConvertHelper.ValueParse(value); + } + + + #endregion + public static Type TypeOf(object type) { return type.GetType(); } - public static void Print(object value) { SereinEnv.WriteLine(InfoType.INFO, value?.ToString()); diff --git a/Workbench/App.xaml.cs b/Workbench/App.xaml.cs index 708ae32..7e3d60d 100644 --- a/Workbench/App.xaml.cs +++ b/Workbench/App.xaml.cs @@ -32,9 +32,11 @@ namespace Serein.Workbench collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); + collection.AddSingleton(); + collection.AddSingleton(); collection.AddTransient(); // 画布 - collection.AddTransient(); // 画布节点树视图 + collection.AddTransient(); // 画布节点树视图 } public static void AddWorkbenchServices(this IServiceCollection collection) @@ -42,6 +44,7 @@ namespace Serein.Workbench collection.AddSingleton(); // 流程事件管理 collection.AddSingleton();// 按键事件管理 collection.AddSingleton(); // 流程事件管理 + collection.AddSingleton(); // 项目管理 collection.AddSingleton(); // 节点操作管理 } @@ -104,69 +107,45 @@ namespace Serein.Workbench collection.AddViewModelServices(); var services = collection.BuildServiceProvider(); // 绑定并返回获取实例的服务接口 App.ServiceProvider = services; - - - - +#if DEBUG _ = this.LoadLocalProjectAsync(); - - } - - - - private async Task LoadLocalProjectAsync() - { - await Task.Delay(500); -#if DEBUG - if (1 == 1) - { - // 这里是测试代码,可以删除 - string filePath; - filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\Release\net8.0\PLCproject.dnf"; - filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\Release\banyunqi\project.dnf"; - filePath = @"F:\临时\project\project.dnf"; - filePath = @"F:\TempFile\flow\qrcode\project.dnf"; - filePath = @"F:\TempFile\flow\temp2\project.dnf"; - if (File.Exists(filePath)) - { - string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 - App.FlowProjectData = JsonConvert.DeserializeObject(content); - App.FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;// - var dir = Path.GetDirectoryName(filePath); - App.GetService().LoadProject(new FlowEnvInfo { Project = App.FlowProjectData }, App.FileDataPath); - } - - - } #endif } - +#if DEBUG + + // 这里是测试代码,可以删除 + private async Task LoadLocalProjectAsync() + { + var projectService = App.GetService(); + await Task.Delay(500); + if (1 == 1) + { + string filePath; + filePath = @"F:\TempFile\flow\temp2\project.dnf"; + projectService.LoadLocalProject(filePath); + } + + } +#endif - public static SereinProjectData? FlowProjectData { get; set; } - public static string FileDataPath { get; set; } = ""; private async void Application_Startup(object sender, StartupEventArgs e) { - // 检查是否传入了参数 + var projectService = App.GetService(); if (e.Args.Length == 1) { - // 获取文件路径 string filePath = e.Args[0]; - // 检查文件是否存在 - if (!System.IO.File.Exists(filePath)) + if (!System.IO.File.Exists(filePath)) // 检查文件是否存在 { MessageBox.Show($"文件未找到:{filePath}"); Shutdown(); // 关闭应用程序 return; } - try { // 读取文件内容 - string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 - FlowProjectData = JsonConvert.DeserializeObject(content); - FileDataPath = System.IO.Path.GetDirectoryName(filePath) ?? ""; + projectService.LoadLocalProject(filePath); } catch (Exception ex) { diff --git a/Workbench/Customs/FlowMethodInfoListBox.xaml b/Workbench/Customs/FlowMethodInfoListBox.xaml index 68e67d0..7c215cd 100644 --- a/Workbench/Customs/FlowMethodInfoListBox.xaml +++ b/Workbench/Customs/FlowMethodInfoListBox.xaml @@ -11,7 +11,8 @@ - diff --git a/Workbench/Customs/FlowMethodInfoListBox.xaml.cs b/Workbench/Customs/FlowMethodInfoListBox.xaml.cs index 40d89b1..30df405 100644 --- a/Workbench/Customs/FlowMethodInfoListBox.xaml.cs +++ b/Workbench/Customs/FlowMethodInfoListBox.xaml.cs @@ -1,6 +1,7 @@ using Serein.Library; using Serein.Library.Utils; using Serein.Workbench.Models; +using Serein.Workbench.Services; using System; using System.Collections; using System.Collections.Generic; @@ -39,9 +40,23 @@ namespace Serein.Workbench.Customs /// /// FlowMethodInfoListBox.xaml 的交互逻辑 /// - public partial class FlowMethodInfoListBox : UserControl + public partial class FlowMethodInfoListBox : UserControl,System.ComponentModel.INotifyPropertyChanged { + private object viewMethodInfo; + public object ViewMethodInfo + { + get => viewMethodInfo; + set + { + if (viewMethodInfo != value) + { + viewMethodInfo = value; + PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(ViewMethodInfo))); + } + } + } + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; public FlowMethodInfoListBox() { @@ -130,5 +145,20 @@ namespace Serein.Workbench.Customs } } + private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if(sender is ListBox listBox) + { + if (listBox.SelectedIndex != -1) + { + var item = listBox.SelectedItem; + if (item is MethodDetailsInfo mdInfo) + { + App.GetService().CurrentMethodDetailsInfo = mdInfo; + } + } + // Serein.Workbench.Models.FlowLibraryInfo + } + } } } diff --git a/Workbench/Node/View/ActionNodeControl.xaml b/Workbench/Node/View/ActionNodeControl.xaml index 8515fd3..69ad89d 100644 --- a/Workbench/Node/View/ActionNodeControl.xaml +++ b/Workbench/Node/View/ActionNodeControl.xaml @@ -64,7 +64,7 @@ - + diff --git a/Workbench/Node/View/ConditionNodeControl.xaml b/Workbench/Node/View/ConditionNodeControl.xaml index ec950e3..ff302ea 100644 --- a/Workbench/Node/View/ConditionNodeControl.xaml +++ b/Workbench/Node/View/ConditionNodeControl.xaml @@ -39,7 +39,7 @@ - + diff --git a/Workbench/Node/View/ConditionNodeControl.xaml.cs b/Workbench/Node/View/ConditionNodeControl.xaml.cs index 950bab1..36bcaae 100644 --- a/Workbench/Node/View/ConditionNodeControl.xaml.cs +++ b/Workbench/Node/View/ConditionNodeControl.xaml.cs @@ -17,13 +17,14 @@ namespace Serein.Workbench.Node.View base.ViewModel = new ConditionNodeControlViewModel (new SingleConditionNode(env)); base.ViewModel.IsEnabledOnView = false; DataContext = ViewModel; - + base.ViewModel.NodeModel.DisplayName = "[条件节点]"; InitializeComponent(); } public ConditionNodeControl(ConditionNodeControlViewModel viewModel):base(viewModel) { DataContext = viewModel; + viewModel.NodeModel.DisplayName = "[条件节点]"; InitializeComponent(); } diff --git a/Workbench/Node/View/ExpOpNodeControl.xaml b/Workbench/Node/View/ExpOpNodeControl.xaml index c6e25ed..ac59910 100644 --- a/Workbench/Node/View/ExpOpNodeControl.xaml +++ b/Workbench/Node/View/ExpOpNodeControl.xaml @@ -27,7 +27,7 @@ - + diff --git a/Workbench/Node/View/ExpOpNodeControl.xaml.cs b/Workbench/Node/View/ExpOpNodeControl.xaml.cs index bfd7369..b14f140 100644 --- a/Workbench/Node/View/ExpOpNodeControl.xaml.cs +++ b/Workbench/Node/View/ExpOpNodeControl.xaml.cs @@ -15,12 +15,14 @@ namespace Serein.Workbench.Node.View var env = App.GetService(); ViewModel = new ExpOpNodeControlViewModel(new SingleExpOpNode(env)); base.ViewModel.IsEnabledOnView = false; + base.ViewModel.NodeModel.DisplayName = "[表达式]"; DataContext = ViewModel; InitializeComponent(); } public ExpOpNodeControl(ExpOpNodeControlViewModel viewModel) :base(viewModel) { DataContext = viewModel; + viewModel.NodeModel.DisplayName = "[表达式]"; InitializeComponent(); } diff --git a/Workbench/Node/View/GlobalDataControl.xaml b/Workbench/Node/View/GlobalDataControl.xaml index 481dacb..d13189a 100644 --- a/Workbench/Node/View/GlobalDataControl.xaml +++ b/Workbench/Node/View/GlobalDataControl.xaml @@ -37,7 +37,7 @@ - + diff --git a/Workbench/Node/View/GlobalDataControl.xaml.cs b/Workbench/Node/View/GlobalDataControl.xaml.cs index bdf7018..75c8fee 100644 --- a/Workbench/Node/View/GlobalDataControl.xaml.cs +++ b/Workbench/Node/View/GlobalDataControl.xaml.cs @@ -16,6 +16,7 @@ namespace Serein.Workbench.Node.View var env = App.GetService(); base.ViewModel = new GlobalDataNodeControlViewModel(new SingleGlobalDataNode(env)); base.ViewModel.IsEnabledOnView = false; + base.ViewModel.NodeModel.DisplayName = "[全局数据]"; DataContext = ViewModel; InitializeComponent(); } @@ -23,6 +24,7 @@ namespace Serein.Workbench.Node.View public GlobalDataControl(GlobalDataNodeControlViewModel viewModel) : base(viewModel) { DataContext = viewModel; + viewModel.NodeModel.DisplayName = "[全局数据]"; InitializeComponent(); } diff --git a/Workbench/Node/View/ScriptNodeControl.xaml.cs b/Workbench/Node/View/ScriptNodeControl.xaml.cs index 5dfb829..5fb0ae7 100644 --- a/Workbench/Node/View/ScriptNodeControl.xaml.cs +++ b/Workbench/Node/View/ScriptNodeControl.xaml.cs @@ -73,7 +73,8 @@ namespace Serein.Workbench.Node.View // 获取 MethodDetailsControl 实例 var methodDetailsControl = this.MethodDetailsControl; var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl - if (itemsControl != null) + + if (itemsControl != null && base.ViewModel.NodeModel.MethodDetails != null) { var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; var controls = new List(); diff --git a/Workbench/Node/View/UINodeControl.xaml b/Workbench/Node/View/UINodeControl.xaml index 3c3621a..9c5ec8f 100644 --- a/Workbench/Node/View/UINodeControl.xaml +++ b/Workbench/Node/View/UINodeControl.xaml @@ -27,13 +27,14 @@ - + + - + diff --git a/Workbench/Node/View/UINodeControl.xaml.cs b/Workbench/Node/View/UINodeControl.xaml.cs index a7fa9e1..0701389 100644 --- a/Workbench/Node/View/UINodeControl.xaml.cs +++ b/Workbench/Node/View/UINodeControl.xaml.cs @@ -26,6 +26,7 @@ namespace Serein.Workbench.Node.View public UINodeControl() { base.ViewModel.IsEnabledOnView = true; + base.ViewModel.NodeModel.DisplayName = "[流程UI]"; InitializeComponent(); } @@ -33,6 +34,7 @@ namespace Serein.Workbench.Node.View { ViewModel = viewModel; DataContext = viewModel; + ViewModel.NodeModel.DisplayName = "[流程UI]"; InitializeComponent(); diff --git a/Workbench/Services/FlowNodeService.cs b/Workbench/Services/FlowNodeService.cs index 362abe6..cb0e71a 100644 --- a/Workbench/Services/FlowNodeService.cs +++ b/Workbench/Services/FlowNodeService.cs @@ -9,6 +9,7 @@ using Serein.Workbench.Node.ViewModel; using Serein.Workbench.ViewModels; using Serein.Workbench.Views; using System; +using System.Reactive; using System.Text; using System.Threading.Tasks; using System.Windows; @@ -20,7 +21,7 @@ namespace Serein.Workbench.Services /// /// 流程节点管理 /// - public class FlowNodeService + public class FlowNodeService { @@ -44,6 +45,16 @@ namespace Serein.Workbench.Services /// public Action OnViewCanvasChanged{ get; set; } + /// + /// 查看的节点发生变化 + /// + public Action OnViewNodeControlChanged{ get; set; } + + /// + /// 查看方法发生变化 + /// + public Action OnViewMethodDetailsInfoChanged { get; set; } + #endregion #region 创建节点相关的属性 @@ -64,10 +75,39 @@ namespace Serein.Workbench.Services } } + + private NodeControlBase? currentSelectNodeControl; + + /// + /// 当前选中的节点 + /// + public NodeControlBase? CurrentSelectNodeControl { get => currentSelectNodeControl; set + { + if (value == null || value.Equals(currentSelectNodeControl)) + { + return; + } + currentSelectNodeControl = value; + OnViewNodeControlChanged?.Invoke(value); + } + } + + private MethodDetailsInfo? currentMethodDetailsInfo; + /// /// 当前拖动的方法信息 /// - public MethodDetailsInfo? CurrentDragMdInfo { get; set; } + public MethodDetailsInfo? CurrentMethodDetailsInfo { get => currentMethodDetailsInfo; set + { + if (value == null || value.Equals(currentMethodDetailsInfo)) + { + return; + } + currentMethodDetailsInfo = value; + OnViewMethodDetailsInfoChanged?.Invoke(value); + } + } + /// /// 当前需要创建的节点类型 @@ -79,10 +119,6 @@ namespace Serein.Workbench.Services /// public PositionOfUI? CurrentMouseLocation { get; set; } - /// - /// 当前选中的节点 - /// - public NodeControlBase? CurrentSelectNodeControl { get; set; } /// /// 连接数据 @@ -651,7 +687,7 @@ namespace Serein.Workbench.Services string canvasGuid = model.Guid; NodeControlType nodeType = CurrentNodeControlType; PositionOfUI? position = CurrentMouseLocation; - MethodDetailsInfo? methodDetailsInfo = CurrentDragMdInfo; + MethodDetailsInfo? methodDetailsInfo = CurrentMethodDetailsInfo; if (position is null) { diff --git a/Workbench/Services/FlowProjectService.cs b/Workbench/Services/FlowProjectService.cs new file mode 100644 index 0000000..7bc033e --- /dev/null +++ b/Workbench/Services/FlowProjectService.cs @@ -0,0 +1,60 @@ +using Newtonsoft.Json; +using Serein.Library; +using Serein.Library.Api; +using Serein.NodeFlow.Env; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Workbench.Services +{ + public class FlowProjectService + { + private readonly IFlowEnvironment flowEnvironment; + + public SereinProjectData? FlowProjectData { get; set; } + public string FileDataPath { get; set; } + + public FlowProjectService(IFlowEnvironment flowEnvironment) + { + this.flowEnvironment = flowEnvironment; + } + + public void LoadLocalProject(string filePath) + { + if (File.Exists(filePath)) + { + string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 + this.FlowProjectData = JsonConvert.DeserializeObject(content); + this.FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;// + var dir = Path.GetDirectoryName(filePath); + var flowEnvInfo = new FlowEnvInfo + { + Project = FlowProjectData, + }; + flowEnvironment.LoadProject(flowEnvInfo, FileDataPath); + } + } + + public void SelectProjectFile() + { + System.Windows.Forms.OpenFileDialog openFileDialog = new System.Windows.Forms.OpenFileDialog(); + openFileDialog.Filter = "流程项目文件|*.dnf|所有文件|*.*"; + openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + openFileDialog.Title = "打开项目文件"; + openFileDialog.Multiselect = false; + + // 显示文件对话框 + if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) + { + // 获取用户选择的文件路径 + var projectFile = openFileDialog.FileName; + LoadLocalProject(projectFile); + } + } + + } +} diff --git a/Workbench/ViewModels/Locator.cs b/Workbench/ViewModels/Locator.cs index 322d6f1..7ac6170 100644 --- a/Workbench/ViewModels/Locator.cs +++ b/Workbench/ViewModels/Locator.cs @@ -22,11 +22,13 @@ namespace Serein.Workbench.ViewModels public BaseNodesViewModel BaseNodesViewModel => App.GetService() ?? throw new NotImplementedException(); public FlowLibrarysViewModel FlowLibrarysViewModel => App.GetService() ?? throw new NotImplementedException(); public FlowEditViewModel FlowEditViewModel => App.GetService() ?? throw new NotImplementedException(); + public ViewNodeInfoViewModel NodeInfoViewModel => App.GetService() ?? throw new NotImplementedException(); + public ViewNodeMethodInfoViewModel ViewNodeMethodInfoViewModel => App.GetService() ?? throw new NotImplementedException(); public FlowCanvasViewModel FlowCanvasViewModel => App.GetService() ?? throw new NotImplementedException(); - public CanvasInfoViewModel CanvasNodeTreeViewModel => App.GetService() ?? throw new NotImplementedException(); + public ViewCanvasInfoViewModel CanvasNodeTreeViewModel => App.GetService() ?? throw new NotImplementedException(); public IServiceProvider ServiceProvider { get; } } diff --git a/Workbench/ViewModels/MainMenuBarViewModel.cs b/Workbench/ViewModels/MainMenuBarViewModel.cs index fa08066..2701fa8 100644 --- a/Workbench/ViewModels/MainMenuBarViewModel.cs +++ b/Workbench/ViewModels/MainMenuBarViewModel.cs @@ -10,6 +10,7 @@ namespace Serein.Workbench.ViewModels { private readonly IFlowEnvironment environment; private readonly FlowNodeService flowNodeService; + private readonly FlowProjectService flowProjectService; /// /// 保存项目 @@ -61,10 +62,13 @@ namespace Serein.Workbench.ViewModels - public MainMenuBarViewModel(IFlowEnvironment environment, FlowNodeService flowNodeService) + public MainMenuBarViewModel(IFlowEnvironment environment, + FlowNodeService flowNodeService, + FlowProjectService flowProjectService) { this.environment = environment; this.flowNodeService = flowNodeService; + this.flowProjectService = flowProjectService; SaveProjectCommand = new RelayCommand(SaveProject); // 保存项目 LoadLocalProjectCommand = new RelayCommand(LoadLocalProject); // 加载本地项目 LoadRemoteProjectCommand = new RelayCommand(LoadRemoteProject); // 加载远程项目 @@ -78,12 +82,16 @@ namespace Serein.Workbench.ViewModels OpenEnvOutWindowCommand = new RelayCommand(OpenEnvOutWindow); // 打开运行输出窗口 OpenDynamicCompilerCommand = new RelayCommand(OpenDynamicCompiler); // 打开动态编译仓库窗口 + this.flowProjectService = flowProjectService; } private void SaveProject() => environment.SaveProject(); // 保存项目 private void LoadLocalProject() { - //environment.LoadProject(); // 加载项目 + + flowProjectService.SelectProjectFile(); //选择项目 } + + private void LoadRemoteProject() { } @@ -91,7 +99,7 @@ namespace Serein.Workbench.ViewModels private void RemoteFlowCanvas() => flowNodeService.RemoveFlowCanvas(); - private void StartFlow() => environment.StartFlowAsync([.. flowNodeService.FlowCanvass.Select(c => c.Guid)]); + private void StartUIFlow() => environment.StartFlowAsync([.. flowNodeService.FlowCanvass.Select(c => c.Guid)]); private void StartCurrentCanvasFlow() => environment.StartFlowAsync([flowNodeService.CurrentSelectCanvas.Guid]); private void StopCurrentCanvasFlow() { } private void OpenDynamicCompiler() { } diff --git a/Workbench/ViewModels/CanvasNodeTreeViewModel.cs b/Workbench/ViewModels/ViewCanvasInfoViewModel.cs similarity index 86% rename from Workbench/ViewModels/CanvasNodeTreeViewModel.cs rename to Workbench/ViewModels/ViewCanvasInfoViewModel.cs index 0155c38..eed4617 100644 --- a/Workbench/ViewModels/CanvasNodeTreeViewModel.cs +++ b/Workbench/ViewModels/ViewCanvasInfoViewModel.cs @@ -10,7 +10,7 @@ using System.Threading.Tasks; namespace Serein.Workbench.ViewModels { - internal partial class CanvasInfoViewModel : ObservableObject + internal partial class ViewCanvasInfoViewModel : ObservableObject { private readonly FlowNodeService flowNodeService; @@ -20,7 +20,7 @@ namespace Serein.Workbench.ViewModels [ObservableProperty] private FlowCanvasDetails _model; - public CanvasInfoViewModel(FlowNodeService flowNodeService) + public ViewCanvasInfoViewModel(FlowNodeService flowNodeService) { this.flowNodeService = flowNodeService; this.flowNodeService.OnViewCanvasChanged += OnViewCanvasChanged; @@ -34,5 +34,8 @@ namespace Serein.Workbench.ViewModels { Model = flowCanvas.ViewModel.Model; } + + + } } diff --git a/Workbench/ViewModels/ViewNodeInfoViewModel.cs b/Workbench/ViewModels/ViewNodeInfoViewModel.cs new file mode 100644 index 0000000..1b63962 --- /dev/null +++ b/Workbench/ViewModels/ViewNodeInfoViewModel.cs @@ -0,0 +1,46 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Serein.Library; +using Serein.Workbench.Node.View; +using Serein.Workbench.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Workbench.ViewModels +{ + internal partial class ViewNodeInfoViewModel : ObservableObject + { + private readonly FlowNodeService flowNodeService; + + /// + /// 当前预览的节点 + /// + [ObservableProperty] + private NodeModelBase viewNodeModel; + + public ViewNodeInfoViewModel(FlowNodeService flowNodeService) + { + this.flowNodeService = flowNodeService; + InitEvent(); + } + + private void InitEvent() + { + flowNodeService.OnViewNodeControlChanged += OnViewNodeControlChanged; + } + private void OnViewNodeControlChanged(NodeControlBase viewNodeControl) + { + if(viewNodeControl is null) + { + ViewNodeModel = null; + } + else + { + ViewNodeModel = viewNodeControl.ViewModel.NodeModel; + } + + } + } +} diff --git a/Workbench/ViewModels/ViewNodeMethodInfoViewModel.cs b/Workbench/ViewModels/ViewNodeMethodInfoViewModel.cs new file mode 100644 index 0000000..4bf56dc --- /dev/null +++ b/Workbench/ViewModels/ViewNodeMethodInfoViewModel.cs @@ -0,0 +1,40 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Serein.Library; +using Serein.Workbench.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Metadata; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Workbench.ViewModels +{ + internal partial class ViewNodeMethodInfoViewModel : ObservableObject + { + private readonly FlowNodeService flowNodeService; + + /// + /// 当前预览的节点 + /// + [ObservableProperty] + private MethodDetailsInfo mdInfo; + + public ViewNodeMethodInfoViewModel(FlowNodeService flowNodeService) + { + this.flowNodeService = flowNodeService; + + InitEvent(); + } + private void InitEvent() + { + flowNodeService.OnViewMethodDetailsInfoChanged += OnViewMethodDetailsInfoChanged; + } + private void OnViewMethodDetailsInfoChanged(MethodDetailsInfo methodDetailsInfo) + { + MdInfo = methodDetailsInfo; + } + + } +} diff --git a/Workbench/Views/BaseNodesView.xaml b/Workbench/Views/BaseNodesView.xaml index c3429e3..b47227f 100644 --- a/Workbench/Views/BaseNodesView.xaml +++ b/Workbench/Views/BaseNodesView.xaml @@ -12,11 +12,11 @@ - - - - - + + + + + diff --git a/Workbench/Views/CanvasInfoView.xaml b/Workbench/Views/CanvasInfoView.xaml deleted file mode 100644 index 7b515e4..0000000 --- a/Workbench/Views/CanvasInfoView.xaml +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Workbench/Views/FlowCanvasView.xaml.cs b/Workbench/Views/FlowCanvasView.xaml.cs index 975e07a..5aff73a 100644 --- a/Workbench/Views/FlowCanvasView.xaml.cs +++ b/Workbench/Views/FlowCanvasView.xaml.cs @@ -40,6 +40,7 @@ using MouseEventArgs = System.Windows.Input.MouseEventArgs; using UserControl = System.Windows.Controls.UserControl; using Clipboard = System.Windows.Clipboard; using TextDataFormat = System.Windows.TextDataFormat; +using System.Windows.Media.Animation; namespace Serein.Workbench.Views { @@ -119,7 +120,7 @@ namespace Serein.Workbench.Views private readonly TranslateTransform translateTransform; #endregion - #region 初始化以及相关事件 + #region 初始化 public FlowCanvasView(FlowCanvasDetails model) { @@ -128,12 +129,13 @@ namespace Serein.Workbench.Views this.DataContext = vm; InitializeComponent(); + flowEnvironment = App.GetService(); flowNodeService = App.GetService(); keyEventService = App.GetService(); flowEEForwardingService = App.GetService(); - flowNodeService.OnCreateNode += OnCreateNode; - keyEventService.OnKeyDown += KeyEventService_OnKeyDown; + + //flowEEForwardingService.OnProjectLoaded += FlowEEForwardingService_OnProjectLoaded; // 缩放平移容器 @@ -144,17 +146,10 @@ namespace Serein.Workbench.Views canvasTransformGroup.Children.Add(translateTransform); FlowChartCanvas.RenderTransform = canvasTransformGroup; SetBinding(model); - + InitEvent(); } - - private void FlowEEForwardingService_OnProjectLoaded(ProjectLoadedEventArgs eventArgs) - { - RefreshAllLine(); - } - - /// /// 设置绑定 /// @@ -162,12 +157,12 @@ namespace Serein.Workbench.Views private void SetBinding(FlowCanvasDetails canvasModel) { Binding bindingScaleX = new(nameof(canvasModel.ScaleX)) { Source = canvasModel, Mode = BindingMode.TwoWay }; - BindingOperations.SetBinding(scaleTransform, ScaleTransform.ScaleXProperty, bindingScaleX); + BindingOperations.SetBinding(scaleTransform, ScaleTransform.ScaleXProperty, bindingScaleX); - Binding bindingScaleY = new(nameof(canvasModel.ScaleY)){ Source = canvasModel, Mode = BindingMode.TwoWay }; + Binding bindingScaleY = new(nameof(canvasModel.ScaleY)) { Source = canvasModel, Mode = BindingMode.TwoWay }; BindingOperations.SetBinding(scaleTransform, ScaleTransform.ScaleYProperty, bindingScaleY); - Binding bindingX = new(nameof(canvasModel.ViewX)) { Source = canvasModel, Mode = BindingMode.TwoWay }; + Binding bindingX = new(nameof(canvasModel.ViewX)) { Source = canvasModel, Mode = BindingMode.TwoWay }; BindingOperations.SetBinding(translateTransform, TranslateTransform.XProperty, bindingX); Binding bindingY = new(nameof(canvasModel.ViewY)) { Source = canvasModel, Mode = BindingMode.TwoWay }; @@ -176,17 +171,114 @@ namespace Serein.Workbench.Views } + private void InitEvent() + { + flowNodeService.OnCreateNode += OnCreateNode; + keyEventService.OnKeyDown += KeyEventService_OnKeyDown; + flowEEForwardingService.OnNodeLocated += FlowEEForwardingService_OnNodeLocated; + } + + /// + /// 节点需要定位 + /// + /// + /// + private void FlowEEForwardingService_OnNodeLocated(NodeLocatedEventArgs eventArgs) + { + if(!ViewModel.NodeControls.TryGetValue(eventArgs.NodeGuid,out var nodeControl)) + { + return; + } + /*if (nodeControl.FlowCanvas.Guid.Equals(Guid)) // 防止事件传播到其它画布 + { + return; + }*/ + + // 获取控件在 FlowChartCanvas 上的相对位置 +#if false + Rect controlBounds = VisualTreeHelper.GetDescendantBounds(nodeControl); + Point controlPosition = nodeControl.TransformToAncestor(FlowChartCanvas).Transform(new Point(0, 0)); + + // 获取控件在画布上的中心点 + double controlCenterX = controlPosition.X + controlBounds.Width / 2; + double controlCenterY = controlPosition.Y + controlBounds.Height / 2; + + // 考虑缩放因素计算目标位置的中心点 + double scaledCenterX = controlCenterX * scaleTransform.ScaleX; + double scaledCenterY = controlCenterY * scaleTransform.ScaleY; + + // 计算平移偏移量,使得控件在可视区域的中心 + double translateX = scaledCenterX - this.FlowChartStackPanel.ActualWidth / 2; + double translateY = scaledCenterY - FlowChartStackPanel.ActualHeight / 2; + + var translate = this.translateTransform; + // 应用平移变换 + translate.X = 0; + translate.Y = 0; + translate.X -= translateX; + translate.Y -= translateY; +#endif + + // 设置RenderTransform以实现移动效果 + TranslateTransform translateTransform = new TranslateTransform(); + nodeControl.RenderTransform = translateTransform; + ElasticAnimation(nodeControl, translateTransform, 6, 0.5, 0.5); + } + /// + /// 控件抖动 + /// 来源:https://www.cnblogs.com/RedSky/p/17705411.html + /// 作者:HotSky + /// + /// + /// 需要抖动的控件 + /// 抖动第一下偏移量 + /// 减弱幅度(小于等于power,大于0) + /// 持续系数(大于0),越大时间越长, + private static void ElasticAnimation(NodeControlBase nodeControl, TranslateTransform translate, double power, double range = 1, double speed = 1) + { + DoubleAnimationUsingKeyFrames animation1 = new DoubleAnimationUsingKeyFrames(); + for (double i = power, j = 1; i >= 0; i -= range) + { + animation1.KeyFrames.Add(new LinearDoubleKeyFrame(-i, TimeSpan.FromMilliseconds(j++ * 100 * speed))); + animation1.KeyFrames.Add(new LinearDoubleKeyFrame(i, TimeSpan.FromMilliseconds(j++ * 100 * speed))); + } + translate.BeginAnimation(TranslateTransform.YProperty, animation1); + DoubleAnimationUsingKeyFrames animation2 = new DoubleAnimationUsingKeyFrames(); + for (double i = power, j = 1; i >= 0; i -= range) + { + animation2.KeyFrames.Add(new LinearDoubleKeyFrame(-i, TimeSpan.FromMilliseconds(j++ * 100 * speed))); + animation2.KeyFrames.Add(new LinearDoubleKeyFrame(i, TimeSpan.FromMilliseconds(j++ * 100 * speed))); + } + translate.BeginAnimation(TranslateTransform.XProperty, animation2); + + animation2.Completed += (s, e) => + { + nodeControl.RenderTransform = null; // 或者重新设置为默认值 + }; + } + + + /// + /// 加载完成后刷新显示 + /// + /// + private void FlowEEForwardingService_OnProjectLoaded(ProjectLoadedEventArgs eventArgs) + { + RefreshAllLine(); + } + + + /// /// 当前画布创建了节点 /// /// private void OnCreateNode(NodeControlBase nodeControl) { - if (!nodeControl.FlowCanvas.Guid.Equals(Guid)) - { - // 防止事件传播到其它画布 - return; - } + if (!nodeControl.FlowCanvas.Guid.Equals(Guid)) // 防止事件传播到其它画布 + { + return; + } var p = nodeControl.ViewModel.NodeModel.Position; PositionOfUI position = new PositionOfUI(p.X, p.Y); if (TryPlaceNodeInRegion(nodeControl, position, out var regionControl)) // 判断添加到区域容器 @@ -223,14 +315,14 @@ namespace Serein.Workbench.Views // 准备放置条件表达式控件 if (nodeControl.ViewModel.NodeModel.ControlType == NodeControlType.ExpCondition) { - /* ConditionRegionControl? conditionRegion = WpfFuncTool.GetParentOfType(hitElement); - if (conditionRegion is not null) - { - targetNodeControl = conditionRegion; - //// 如果存在条件区域容器 - //conditionRegion.AddCondition(nodeControl); - return true; - }*/ + /* ConditionRegionControl? conditionRegion = WpfFuncTool.GetParentOfType(hitElement); + if (conditionRegion is not null) + { + targetNodeControl = conditionRegion; + //// 如果存在条件区域容器 + //conditionRegion.AddCondition(nodeControl); + return true; + }*/ } else @@ -246,11 +338,13 @@ namespace Serein.Workbench.Views } targetNodeControl = null; return false; - } - + } #endregion - #region 画布接口实现 + + + + #region 接口实现 private IFlowCanvas Api => this; public string Guid @@ -273,7 +367,7 @@ namespace Serein.Workbench.Views } void IFlowCanvas.Add(NodeControlBase nodeControl) { - + ViewModel.NodeControls.TryAdd(nodeControl.ViewModel.NodeModel.Guid, nodeControl); FlowChartCanvas.Dispatcher.Invoke(() => @@ -356,7 +450,7 @@ namespace Serein.Workbench.Views //} } } - void IFlowCanvas.CreateArgConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl,ConnectionArgSourceType type, int index) + void IFlowCanvas.CreateArgConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl, ConnectionArgSourceType type, int index) { if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) { @@ -377,7 +471,8 @@ namespace Serein.Workbench.Views _ = Task.Run(async () => { await Task.Delay(100); - await App.UIContextOperation.InvokeAsync(() => { + await App.UIContextOperation.InvokeAsync(() => + { Api.CreateArgConnection(fromNodeControl, toNodeControl, type, index); }); }); @@ -651,7 +746,7 @@ namespace Serein.Workbench.Views if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeModel nodeModel) { flowNodeService.CurrentNodeControlType = nodeModel.NodeControlType; // 设置基础节点类型 - flowNodeService.CurrentDragMdInfo = nodeModel.MethodDetailsInfo; // 基础节点不需要参数信息 + flowNodeService.CurrentMethodDetailsInfo = nodeModel.MethodDetailsInfo; // 基础节点不需要参数信息 flowNodeService.CurrentMouseLocation = position; // 设置当前鼠标为止 flowNodeService.CreateNode(); // 创建来自DLL加载的方法节点 } @@ -674,7 +769,7 @@ namespace Serein.Workbench.Views if (nodeControlType != NodeControlType.None) { flowNodeService.CurrentNodeControlType = nodeControlType; // 设置基础节点类型 - flowNodeService.CurrentDragMdInfo = null; // 基础节点不需要参数信息 + flowNodeService.CurrentMethodDetailsInfo = null; // 基础节点不需要参数信息 flowNodeService.CurrentMouseLocation = position; // 设置当前鼠标为止 flowNodeService.CreateNode(); // 创建基础节点 @@ -1095,6 +1190,10 @@ namespace Serein.Workbench.Views } if (selectNodeControls.Count == 1) { + var nodeConotrol = selectNodeControls[0]; + // 选取了控件 + flowNodeService.CurrentSelectNodeControl = nodeConotrol; // 更新选取节点显示 + App.GetService().CurrentMethodDetailsInfo = nodeConotrol.ViewModel.NodeModel.MethodDetails.ToInfo(); // ChangeViewerObjOfNode(selectNodeControls[0]); } diff --git a/Workbench/Views/FlowWorkbenchView.xaml b/Workbench/Views/FlowWorkbenchView.xaml index 24ed111..3948c03 100644 --- a/Workbench/Views/FlowWorkbenchView.xaml +++ b/Workbench/Views/FlowWorkbenchView.xaml @@ -14,9 +14,9 @@ - + - + @@ -31,16 +31,34 @@ - - + + - + - + - + + + + + + + + + + + + + + + + + + + + - diff --git a/Workbench/Views/ViewCanvasInfoView.xaml b/Workbench/Views/ViewCanvasInfoView.xaml new file mode 100644 index 0000000..4171ad3 --- /dev/null +++ b/Workbench/Views/ViewCanvasInfoView.xaml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Workbench/Views/ViewCanvasInfoView.xaml.cs b/Workbench/Views/ViewCanvasInfoView.xaml.cs new file mode 100644 index 0000000..303f572 --- /dev/null +++ b/Workbench/Views/ViewCanvasInfoView.xaml.cs @@ -0,0 +1,51 @@ +using Serein.Library; +using Serein.Library.Api; +using Serein.Workbench.ViewModels; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Serein.Workbench.Views +{ + /// + /// CanvasNodeTreeView.xaml 的交互逻辑 + /// + public partial class ViewCanvasInfoView : UserControl + { + private readonly ViewCanvasInfoViewModel ViewModel; + private readonly ViewNodeInfoViewModel NodeInfoViewModel; + public ViewCanvasInfoView() + { + this.ViewModel = App.GetService(); + this.NodeInfoViewModel = App.GetService(); + this.DataContext = this.ViewModel; + InitializeComponent(); + } + + private void Grid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (sender is Grid grid && grid.DataContext is NodeModelBase nodeModel) + { + NodeInfoViewModel.ViewNodeModel = nodeModel; + App.GetService().NodeLocated(nodeModel.Guid); + } + + // 定位节点 + //if (e.ClickCount == 2) + //{ + //} + } + } +} diff --git a/Workbench/Views/ViewNodeInfoView.xaml b/Workbench/Views/ViewNodeInfoView.xaml new file mode 100644 index 0000000..c34731e --- /dev/null +++ b/Workbench/Views/ViewNodeInfoView.xaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Workbench/Views/CanvasInfoView.xaml.cs b/Workbench/Views/ViewNodeInfoView.xaml.cs similarity index 64% rename from Workbench/Views/CanvasInfoView.xaml.cs rename to Workbench/Views/ViewNodeInfoView.xaml.cs index 3bf02f0..9518706 100644 --- a/Workbench/Views/CanvasInfoView.xaml.cs +++ b/Workbench/Views/ViewNodeInfoView.xaml.cs @@ -17,15 +17,16 @@ using System.Windows.Shapes; namespace Serein.Workbench.Views { /// - /// CanvasNodeTreeView.xaml 的交互逻辑 + /// NodeInfoView.xaml 的交互逻辑 /// - public partial class CanvasInfoView : UserControl + public partial class ViewNodeInfoView : UserControl { - private readonly CanvasInfoViewModel ViewModel; - public CanvasInfoView() + private readonly ViewNodeInfoViewModel ViewModel; + public ViewNodeInfoView() { - this.ViewModel = App.GetService(); - this.DataContext = this.ViewModel; + + ViewModel = App.GetService().NodeInfoViewModel; + this.DataContext = ViewModel; InitializeComponent(); } } diff --git a/Workbench/Views/ViewNodeMethodInfoView.xaml b/Workbench/Views/ViewNodeMethodInfoView.xaml new file mode 100644 index 0000000..8fd5411 --- /dev/null +++ b/Workbench/Views/ViewNodeMethodInfoView.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Workbench/Views/ViewNodeMethodInfoView.xaml.cs b/Workbench/Views/ViewNodeMethodInfoView.xaml.cs new file mode 100644 index 0000000..c64533b --- /dev/null +++ b/Workbench/Views/ViewNodeMethodInfoView.xaml.cs @@ -0,0 +1,33 @@ +using Serein.Workbench.ViewModels; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Serein.Workbench.Views +{ + /// + /// NodeMethodInfoView.xaml 的交互逻辑 + /// + public partial class ViewNodeMethodInfoView : UserControl + { + private readonly ViewNodeMethodInfoViewModel ViewModel; + + public ViewNodeMethodInfoView() + { + ViewModel = App.GetService().ViewNodeMethodInfoViewModel; + this.DataContext = ViewModel; + InitializeComponent(); + } + } +}