mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-03 00:00:49 +08:00
新增了UI节点
This commit is contained in:
41
Library/Api/IEmbeddedContent.cs
Normal file
41
Library/Api/IEmbeddedContent.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Api
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 流程中的控件
|
||||
/// </summary>
|
||||
public interface IFlowControl
|
||||
{
|
||||
/// <summary>
|
||||
/// 节点执行事件
|
||||
/// </summary>
|
||||
void OnExecuting(object data);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义UI显示
|
||||
/// </summary>
|
||||
public interface IEmbeddedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取用户控件(WPF)
|
||||
/// </summary>
|
||||
object GetUserControl();
|
||||
|
||||
/// <summary>
|
||||
/// 获取窗体控件
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IFlowControl GetFlowControl();
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -29,6 +29,12 @@ namespace Serein.Library
|
||||
/// </summary>
|
||||
Exit,
|
||||
|
||||
/// <summary>
|
||||
/// <para>UI节点(每个节点只会执行一次对应的方法)</para>
|
||||
/// <para>需要返回IEmbeddedContent接口</para>
|
||||
/// <para>IEmbeddedContent接口实现由你决定</para>
|
||||
/// </summary>
|
||||
UI,
|
||||
|
||||
/// <summary>
|
||||
/// <para>触发器节点,必须为标记在可异步等待的方法,建议与继承了 FlowTriggerk<TEnum> 的实例对象搭配使用</para>
|
||||
@@ -88,6 +94,11 @@ namespace Serein.Library
|
||||
/// </summary>
|
||||
Flipflop,
|
||||
|
||||
/// <summary>
|
||||
/// UI节点
|
||||
/// </summary>
|
||||
UI,
|
||||
|
||||
/// <summary>
|
||||
/// 表达式操作节点
|
||||
/// </summary>
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using static Serein.Library.Utils.EmitHelper;
|
||||
@@ -27,7 +28,7 @@ namespace Serein.Library
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*/// <summary>
|
||||
/// 更新委托方法
|
||||
/// </summary>
|
||||
|
||||
@@ -379,8 +379,12 @@ namespace Serein.Library
|
||||
if (md.ActingInstance is null)
|
||||
{
|
||||
md.ActingInstance = context.Env.IOC.Get(md.ActingInstanceType);
|
||||
if (md.ActingInstance is null)
|
||||
{
|
||||
md.ActingInstance = context.Env.IOC.Instantiate(md.ActingInstanceType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object[] args = await GetParametersAsync(context);
|
||||
var result = await dd.InvokeAsync(md.ActingInstance, args);
|
||||
return result;
|
||||
|
||||
@@ -9,8 +9,6 @@ namespace Serein.Library.Utils
|
||||
public class ArrayHelper
|
||||
{
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 数组尾部扩容
|
||||
/// </summary>
|
||||
|
||||
@@ -8,6 +8,9 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.Library.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 通过字典构造动态对象
|
||||
/// </summary>
|
||||
public class DynamicObjectHelper
|
||||
{
|
||||
// 类型缓存,键为类型的唯一名称(可以根据实际需求调整生成方式)
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace Serein.Library.Utils
|
||||
public static class ObjectConvertHelper
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 父类转为子类
|
||||
/// </summary>
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace Net462DllTest.Web
|
||||
{
|
||||
this.plcDevice = plcDevice;
|
||||
this.viewManagement = viewManagement;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace Serein.NodeFlow.Env
|
||||
this.FlowLibraryManagement = new FlowLibraryManagement(this); // 实例化类库管理
|
||||
|
||||
#region 注册基本节点类型
|
||||
NodeMVVMManagement.RegisterModel(NodeControlType.UI, typeof(SingleUINode)); // 动作节点
|
||||
|
||||
NodeMVVMManagement.RegisterModel(NodeControlType.Action, typeof(SingleActionNode)); // 动作节点
|
||||
NodeMVVMManagement.RegisterModel(NodeControlType.Flipflop, typeof(SingleFlipflopNode)); // 触发器节点
|
||||
NodeMVVMManagement.RegisterModel(NodeControlType.ExpOp, typeof(SingleExpOpNode)); // 表达式节点
|
||||
|
||||
@@ -39,8 +39,6 @@ namespace Serein.NodeFlow.Env
|
||||
await SendCommandFunc.Invoke(msgId, theme, data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 发送请求
|
||||
/// </summary>
|
||||
|
||||
@@ -33,7 +33,6 @@ namespace Serein.NodeFlow.Env
|
||||
});
|
||||
}
|
||||
|
||||
//private readonly Func<string, object?, Task> SendCommandAsync;
|
||||
private readonly RemoteMsgUtil RemoteMsgUtil;
|
||||
private readonly MsgControllerOfClient msgClient;
|
||||
private readonly ConcurrentDictionary<string, MethodDetails> MethodDetailss = [];
|
||||
@@ -46,9 +45,6 @@ namespace Serein.NodeFlow.Env
|
||||
|
||||
public event LoadDllHandler OnDllLoad;
|
||||
public event ProjectLoadedHandler OnProjectLoaded;
|
||||
/// <summary>
|
||||
/// 项目准备保存
|
||||
/// </summary>
|
||||
public event ProjectSavingHandler? OnProjectSaving;
|
||||
public event NodeConnectChangeHandler OnNodeConnectChange;
|
||||
public event NodeCreateHandler OnNodeCreate;
|
||||
@@ -93,14 +89,7 @@ namespace Serein.NodeFlow.Env
|
||||
/// </summary>
|
||||
private bool IsLoadingNode = false;
|
||||
|
||||
//public void SetConsoleOut()
|
||||
//{
|
||||
// var logTextWriter = new LogTextWriter(msg =>
|
||||
// {
|
||||
// OnEnvOut?.Invoke(msg);
|
||||
// });
|
||||
// Console.SetOut(logTextWriter);
|
||||
//}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 输出信息
|
||||
@@ -454,6 +443,7 @@ namespace Serein.NodeFlow.Env
|
||||
/// <returns>被设置为起始节点的Guid</returns>
|
||||
public async Task<string> SetStartNodeAsync(string nodeGuid)
|
||||
{
|
||||
|
||||
var newNodeGuid = await msgClient.SendAndWaitDataAsync<string>(EnvMsgTheme.SetStartNode, new
|
||||
{
|
||||
nodeGuid
|
||||
|
||||
44
NodeFlow/Model/SingleUINode.cs
Normal file
44
NodeFlow/Model/SingleUINode.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Serein.NodeFlow.Model
|
||||
{
|
||||
public class SingleUINode : NodeModelBase
|
||||
{
|
||||
public IEmbeddedContent Adapter { get; private set; }
|
||||
public SingleUINode(IFlowEnvironment environment) : base(environment)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<object> ExecutingAsync(IDynamicContext context)
|
||||
{
|
||||
if(Adapter is null)
|
||||
{
|
||||
|
||||
var result = await base.ExecutingAsync(context);
|
||||
if (result is IEmbeddedContent adapter)
|
||||
{
|
||||
this.Adapter = adapter;
|
||||
context.NextOrientation = ConnectionInvokeType.IsSucceed;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.NextOrientation = ConnectionInvokeType.IsError;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var p = context.GetPreviousNode(this);
|
||||
var data = context.GetFlowData(p.Guid);
|
||||
Adapter.GetFlowControl().OnExecuting(data);
|
||||
}
|
||||
|
||||
return Task.FromResult<object?>(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.1.0</Version>
|
||||
<Version>1.1.1</Version>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
64
README.md
64
README.md
@@ -1,5 +1,5 @@
|
||||
# 自述
|
||||
基于WPF(Dotnet 8)的流程可视化编辑器,需二次开发。
|
||||
基于Dotnet 8 的流程可视化编辑器,需二次开发。
|
||||
不定期在Bilibili个人空间上更新相关的视频。
|
||||
https://space.bilibili.com/33526379
|
||||
|
||||
@@ -16,6 +16,7 @@ https://space.bilibili.com/33526379
|
||||
使用 **NodeAction** 特性标记你的方法。
|
||||
* 动作节点 - Action
|
||||
* 触发器节点 - Flipflop
|
||||
* UI节点 - UI
|
||||
# 关于 IDynamicContext 说明(重要)**
|
||||
* 基本说明:IDynamicContext 是节点之间传递数据的接口、载体,其实例由 FlowEnvironment 运行环境自动实现,内部提供全局单例的环境接口,用以注册、获取实例(单例模式),一般情况下,你无须关注 FlowEnvironment 对外暴露的属性方法。
|
||||
* 重要概念:
|
||||
@@ -25,10 +26,10 @@ https://space.bilibili.com/33526379
|
||||
* 简述:枚举,标识流程运行的状态(初始化,运行中,运行完成)
|
||||
* 场景:类库代码中创建了运行时间较长的异步任务、或开辟了另一个线程进行循环操作时,可以在方法入参定义一个 IDynamicContext 类型入参,然后在代码中使用形成闭包,以及时判断流程是否已经结束。另外的,如果想监听项目停止运行,可以订阅 context.Env.OnFlowRunComplete 事件。
|
||||
* NextOrientation - 即将进入的分支:
|
||||
* 简述:流程分支枚举, Upstream(上游分支)、IsSucceed(真分支)、IsFail(假分支),IsError(异常分支)。
|
||||
* 场景:允许你在类库代码中操作该属性,手动控制当前节点运行完成后,下一个会执行哪一个类别的节点。
|
||||
* Exit() - 结束流程
|
||||
* 简述:顾名思义,能够让你在类库代码中提前结束当前流程运行
|
||||
* 简述:流程分支枚举, Upstream(上游分支)、IsSucceed(真分支)、IsFail(假分支),IsError(异常分支)。
|
||||
* 场景:允许你在类库代码中操作该属性,手动控制当前节点运行完成后,下一个会执行哪一个类别的节点。
|
||||
* Exit() - 结束流程
|
||||
* 简述:顾名思义,能够让你在类库代码中提前结束当前流程运行
|
||||
|
||||
# 关于 DynamicNodeType 枚举的补充说明。
|
||||
## 1. 不生成节点控件的枚举值:
|
||||
@@ -156,13 +157,60 @@ https://space.bilibili.com/33526379
|
||||
* 入参:依照Action节点。
|
||||
* 返回值:Task`<IFlipflopContext<TResult>>`
|
||||
* 描述:接收上一节点传递的上下文,同样进入异步等待,但执行完成后不会再次等待自身(只会触发一次)。
|
||||
* IFlipflopContext`<TResult>`
|
||||
* 关于 IFlipflopContext`<TResult>` 接口
|
||||
* 基本说明:IFlipflopContext是一个接口,你无须关心内部实现。
|
||||
* 参数描述:State,状态枚举描述(Succeed、Cancel、Error、Cancel),如果返回Cancel,则不会执行后继分支,如果返回其它状态,则会获取对应的后继分支,开始执行。
|
||||
* 参数描述:Type,触发状态描述(External外部触发,Overtime超时触发),当你在代码中的其他地方主动触发了触发器,则该次触发类型为External,当你在创建触发器后超过了指定时间(创建触发器时会要求声明超时时间),则会自动触发,但触发类型为Overtime,触发参数未你在创建触发器时指定的值)
|
||||
* 参数描述:Value,触发时传递的参数。
|
||||
* 使用场景:配合 FlowTrigger`<TEnum>` 使用,例如定时从PLC中获取状态,当某个变量发生改变时,会通知相应的触发器,如果需要,可以传递对应的数据。
|
||||
演示:
|
||||
* 使用场景:配合 FlowTrigger`<TEnum>` 使用,例如定时从PLC中获取状态,当某个变量发生改变时,会通知相应的触发器,如果需要,可以传递对应的数据。
|
||||
* **UI - 自定义控件**
|
||||
* 入参:默认使用上一节点返回值。
|
||||
* 返回值:IEmbeddedContent 接口
|
||||
* 描述:将类库中的WPF UserControl嵌入并显示在一个节点上,显示在工作台UI中。例如在视觉处理流程中,需要即时的显示图片。
|
||||
* 关于 IEmbeddedContent 接口
|
||||
* IEmbeddedContent 需要由你实现,框架并不负责
|
||||
```
|
||||
[DynamicFlow("[界面显示]")]
|
||||
internal class FlowControl
|
||||
{
|
||||
[NodeAction(NodeType.UI)]
|
||||
public async Task<IEmbeddedContent> CreateImageControl(DynamicContext context)
|
||||
{
|
||||
WpfUserControlAdapter adapter = null;
|
||||
// 其实你也可以直接创建实例
|
||||
// 但如果你的实例化操作涉及到了对UI元素修改,还是建议像这里一样使用异步方法
|
||||
await context.Env.UIContextOperation.InvokeAsync(() =>
|
||||
{
|
||||
var userControl = new UserControl();
|
||||
adapter = new WpfUserControlAdapter(userControl, userControl);
|
||||
});
|
||||
return adapter;
|
||||
}
|
||||
}
|
||||
public class WpfUserControlAdapter : IEmbeddedContent
|
||||
{
|
||||
private readonly UserControl userControl;
|
||||
private readonly IFlowControl flowControl;
|
||||
|
||||
public WpfUserControlAdapter(UserControl userControl, IFlowControl flowControl)
|
||||
{
|
||||
this.userControl = userControl;
|
||||
this.flowControl= flowControl;
|
||||
}
|
||||
|
||||
public IFlowControl GetFlowControl()
|
||||
{
|
||||
return flowControl;
|
||||
}
|
||||
|
||||
public object GetUserControl()
|
||||
{
|
||||
return userControl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 演示:
|
||||

|
||||

|
||||

|
||||
|
||||
20
Serein.CloudWorkbench/Components/App.razor
Normal file
20
Serein.CloudWorkbench/Components/App.razor
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="app.css" />
|
||||
<link rel="stylesheet" href="Serein.CloudWorkbench.styles.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
23
Serein.CloudWorkbench/Components/Layout/MainLayout.razor
Normal file
23
Serein.CloudWorkbench/Components/Layout/MainLayout.razor
Normal file
@@ -0,0 +1,23 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<NavMenu />
|
||||
</div>
|
||||
|
||||
<main>
|
||||
<div class="top-row px-4">
|
||||
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
|
||||
</div>
|
||||
|
||||
<article class="content px-4">
|
||||
@Body
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
96
Serein.CloudWorkbench/Components/Layout/MainLayout.razor.css
Normal file
96
Serein.CloudWorkbench/Components/Layout/MainLayout.razor.css
Normal file
@@ -0,0 +1,96 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
30
Serein.CloudWorkbench/Components/Layout/NavMenu.razor
Normal file
30
Serein.CloudWorkbench/Components/Layout/NavMenu.razor
Normal file
@@ -0,0 +1,30 @@
|
||||
<div class="top-row ps-3 navbar navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="">Serein.CloudWorkbench</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
|
||||
|
||||
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
|
||||
<nav class="flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="counter">
|
||||
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="weather">
|
||||
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
|
||||
</NavLink>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
105
Serein.CloudWorkbench/Components/Layout/NavMenu.razor.css
Normal file
105
Serein.CloudWorkbench/Components/Layout/NavMenu.razor.css
Normal file
@@ -0,0 +1,105 @@
|
||||
.navbar-toggler {
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
width: 3.5rem;
|
||||
height: 2.5rem;
|
||||
color: white;
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.navbar-toggler:checked {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
height: 3.5rem;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.bi {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
margin-right: 0.75rem;
|
||||
top: -1px;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.bi-house-door-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-plus-square-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-list-nested-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-item:first-of-type {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-item:last-of-type {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep .nav-link {
|
||||
color: #d7d7d7;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255,255,255,0.37);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item ::deep .nav-link:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-toggler:checked ~ .nav-scrollable {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
/* Never collapse the sidebar for wide screens */
|
||||
display: block;
|
||||
|
||||
/* Allow sidebar to scroll for tall menus */
|
||||
height: calc(100vh - 3.5rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
19
Serein.CloudWorkbench/Components/Pages/Counter.razor
Normal file
19
Serein.CloudWorkbench/Components/Pages/Counter.razor
Normal file
@@ -0,0 +1,19 @@
|
||||
@page "/counter"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Counter</PageTitle>
|
||||
|
||||
<h1>Counter</h1>
|
||||
|
||||
<p role="status">Current count: @currentCount</p>
|
||||
|
||||
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
|
||||
private void IncrementCount()
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
36
Serein.CloudWorkbench/Components/Pages/Error.razor
Normal file
36
Serein.CloudWorkbench/Components/Pages/Error.razor
Normal file
@@ -0,0 +1,36 @@
|
||||
@page "/Error"
|
||||
@using System.Diagnostics
|
||||
|
||||
<PageTitle>Error</PageTitle>
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
|
||||
@code{
|
||||
[CascadingParameter]
|
||||
private HttpContext? HttpContext { get; set; }
|
||||
|
||||
private string? RequestId { get; set; }
|
||||
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
protected override void OnInitialized() =>
|
||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||
}
|
||||
7
Serein.CloudWorkbench/Components/Pages/Home.razor
Normal file
7
Serein.CloudWorkbench/Components/Pages/Home.razor
Normal file
@@ -0,0 +1,7 @@
|
||||
@page "/"
|
||||
|
||||
<PageTitle>Home</PageTitle>
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
64
Serein.CloudWorkbench/Components/Pages/Weather.razor
Normal file
64
Serein.CloudWorkbench/Components/Pages/Weather.razor
Normal file
@@ -0,0 +1,64 @@
|
||||
@page "/weather"
|
||||
@attribute [StreamRendering]
|
||||
|
||||
<PageTitle>Weather</PageTitle>
|
||||
|
||||
<h1>Weather</h1>
|
||||
|
||||
<p>This component demonstrates showing data.</p>
|
||||
|
||||
@if (forecasts == null)
|
||||
{
|
||||
<p><em>Loading...</em></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Temp. (C)</th>
|
||||
<th>Temp. (F)</th>
|
||||
<th>Summary</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var forecast in forecasts)
|
||||
{
|
||||
<tr>
|
||||
<td>@forecast.Date.ToShortDateString()</td>
|
||||
<td>@forecast.TemperatureC</td>
|
||||
<td>@forecast.TemperatureF</td>
|
||||
<td>@forecast.Summary</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
@code {
|
||||
private WeatherForecast[]? forecasts;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Simulate asynchronous loading to demonstrate streaming rendering
|
||||
await Task.Delay(500);
|
||||
|
||||
var startDate = DateOnly.FromDateTime(DateTime.Now);
|
||||
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
|
||||
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
||||
{
|
||||
Date = startDate.AddDays(index),
|
||||
TemperatureC = Random.Shared.Next(-20, 55),
|
||||
Summary = summaries[Random.Shared.Next(summaries.Length)]
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
private class WeatherForecast
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
public int TemperatureC { get; set; }
|
||||
public string? Summary { get; set; }
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
}
|
||||
}
|
||||
6
Serein.CloudWorkbench/Components/Routes.razor
Normal file
6
Serein.CloudWorkbench/Components/Routes.razor
Normal file
@@ -0,0 +1,6 @@
|
||||
<Router AppAssembly="typeof(Program).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
10
Serein.CloudWorkbench/Components/_Imports.razor
Normal file
10
Serein.CloudWorkbench/Components/_Imports.razor
Normal file
@@ -0,0 +1,10 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using Serein.CloudWorkbench
|
||||
@using Serein.CloudWorkbench.Components
|
||||
39
Serein.CloudWorkbench/Program.cs
Normal file
39
Serein.CloudWorkbench/Program.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Serein.CloudWorkbench.Components;
|
||||
using Serein.CloudWorkbench.Services;
|
||||
|
||||
namespace Serein.CloudWorkbench
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
|
||||
|
||||
// ×¢²á CounterService ×÷Ϊµ¥Àý
|
||||
builder.Services.AddSingleton<CounterService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseStaticFiles();
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<DeleteExistingFiles>false</DeleteExistingFiles>
|
||||
<ExcludeApp_Data>false</ExcludeApp_Data>
|
||||
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
|
||||
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
|
||||
<LastUsedPlatform>Any CPU</LastUsedPlatform>
|
||||
<PublishProvider>FileSystem</PublishProvider>
|
||||
<PublishUrl>bin\Release\net8.0\publish\</PublishUrl>
|
||||
<WebPublishMethod>FileSystem</WebPublishMethod>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
38
Serein.CloudWorkbench/Properties/launchSettings.json
Normal file
38
Serein.CloudWorkbench/Properties/launchSettings.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:30763",
|
||||
"sslPort": 44329
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5085",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7230;http://localhost:5085",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Serein.CloudWorkbench/Serein.CloudWorkbench.csproj
Normal file
9
Serein.CloudWorkbench/Serein.CloudWorkbench.csproj
Normal file
@@ -0,0 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
21
Serein.CloudWorkbench/Services/CounterService.cs
Normal file
21
Serein.CloudWorkbench/Services/CounterService.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace Serein.CloudWorkbench.Services
|
||||
{
|
||||
public class CounterService
|
||||
{
|
||||
public int Count { get; private set; } = 0;
|
||||
|
||||
public event Action? OnCountChanged;
|
||||
|
||||
public void Increment()
|
||||
{
|
||||
Count++;
|
||||
OnCountChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void Decrement()
|
||||
{
|
||||
Count--;
|
||||
OnCountChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Serein.CloudWorkbench/appsettings.Development.json
Normal file
8
Serein.CloudWorkbench/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Serein.CloudWorkbench/appsettings.json
Normal file
9
Serein.CloudWorkbench/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
51
Serein.CloudWorkbench/wwwroot/app.css
Normal file
51
Serein.CloudWorkbench/wwwroot/app.css
Normal file
@@ -0,0 +1,51 @@
|
||||
html, body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a, .btn-link {
|
||||
color: #006bb7;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 1.1rem;
|
||||
}
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.valid.modified:not([type=checkbox]) {
|
||||
outline: 1px solid #26b050;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
outline: 1px solid #e50000;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: #e50000;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
|
||||
.darker-border-checkbox.form-check-input {
|
||||
border-color: #929292;
|
||||
}
|
||||
7
Serein.CloudWorkbench/wwwroot/bootstrap/bootstrap.min.css
vendored
Normal file
7
Serein.CloudWorkbench/wwwroot/bootstrap/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
Serein.CloudWorkbench/wwwroot/favicon.png
Normal file
BIN
Serein.CloudWorkbench/wwwroot/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -17,13 +17,13 @@ namespace Serein.Workbench
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
if (1 == 2)
|
||||
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 = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\debug\net8.0\project.dnf";
|
||||
filePath = @"F:\临时\project\project.dnf";
|
||||
//filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\debug\net8.0\test.dnf";
|
||||
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
|
||||
App.FlowProjectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
|
||||
|
||||
@@ -162,6 +162,7 @@ namespace Serein.Workbench
|
||||
IOCObjectViewer.SelectObj += ViewObjectViewer.LoadObjectInformation; // 使选择 IOC容器视图 的某项(对象)时,可以在 数据视图 呈现数据
|
||||
|
||||
#region 为 NodeControlType 枚举 不同项添加对应的 Control类型 、 ViewModel类型
|
||||
NodeMVVMManagement.RegisterUI(NodeControlType.UI, typeof(UINodeControl), typeof(UINodeControlViewModel));
|
||||
NodeMVVMManagement.RegisterUI(NodeControlType.Action, typeof(ActionNodeControl), typeof(ActionNodeControlViewModel));
|
||||
NodeMVVMManagement.RegisterUI(NodeControlType.Flipflop, typeof(FlipflopNodeControl), typeof(FlipflopNodeControlViewModel));
|
||||
NodeMVVMManagement.RegisterUI(NodeControlType.ExpOp, typeof(ExpOpNodeControl), typeof(ExpOpNodeControlViewModel));
|
||||
@@ -492,6 +493,9 @@ namespace Serein.Workbench
|
||||
case Library.NodeType.Flipflop:
|
||||
dllControl.AddFlipflop(methodDetailsInfo); // 添加触发器方法到控件
|
||||
break;
|
||||
case Library.NodeType.UI:
|
||||
dllControl.AddUI(methodDetailsInfo); // 添加触发器方法到控件
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
@@ -26,6 +27,9 @@
|
||||
<GroupBox x:Name="FlipflopNodeGroupBox" Grid.Row="1" Header="触发器" Margin="5">
|
||||
<ListBox x:Name="FlipflopsListBox" Background="#FACFC1"/>
|
||||
</GroupBox>
|
||||
<GroupBox x:Name="UINodeGroupBox" Grid.Row="2" Header="UI" Margin="5">
|
||||
<ListBox x:Name="UIListBox" Background="#FFFBD7"/>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
|
||||
|
||||
|
||||
@@ -6,10 +6,7 @@ using System.Windows.Input;
|
||||
|
||||
namespace Serein.Workbench.Node.View
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// UserControl1.xaml 的交互逻辑
|
||||
@@ -28,8 +25,11 @@ namespace Serein.Workbench.Node.View
|
||||
this.nodeLibraryInfo = nodeLibraryInfo;
|
||||
Header = "DLL name : " + nodeLibraryInfo.AssemblyName;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
FlipflopNodeGroupBox.Visibility = Visibility.Collapsed;
|
||||
ActionNodeGroupBox.Visibility = Visibility.Collapsed;
|
||||
UINodeGroupBox.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -65,6 +65,16 @@ namespace Serein.Workbench.Node.View
|
||||
FlipflopNodeGroupBox.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向触发器面板添加类型的文本块
|
||||
/// </summary>
|
||||
/// <param name="type">要添加的类型</param>
|
||||
public void AddUI(MethodDetailsInfo mdInfo)
|
||||
{
|
||||
AddTypeToListBox(mdInfo, UIListBox);
|
||||
UINodeGroupBox.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向指定面板添加类型的文本块
|
||||
/// </summary>
|
||||
@@ -137,6 +147,7 @@ namespace Serein.Workbench.Node.View
|
||||
{
|
||||
NodeType.Action => NodeControlType.Action,
|
||||
NodeType.Flipflop => NodeControlType.Flipflop,
|
||||
NodeType.UI => NodeControlType.UI,
|
||||
_ => NodeControlType.None,
|
||||
},
|
||||
MethodDetailsInfo = mdInfo,
|
||||
|
||||
40
Workbench/Node/View/UINodeControl.xaml
Normal file
40
Workbench/Node/View/UINodeControl.xaml
Normal file
@@ -0,0 +1,40 @@
|
||||
<local:NodeControlBase x:Class="Serein.Workbench.Node.View.UINodeControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Serein.Workbench.Node.View"
|
||||
xmlns:vm="clr-namespace:Serein.Workbench.Node.ViewModel"
|
||||
xmlns:themes="clr-namespace:Serein.Workbench.Themes"
|
||||
d:DataContext="{d:DesignInstance vm:UINodeControlViewModel}"
|
||||
mc:Ignorable="d"
|
||||
MinWidth="50"
|
||||
Initialized="NodeControlBase_Initialized"
|
||||
Loaded="NodeControlBase_Loaded"
|
||||
>
|
||||
<Grid x:Name="MainGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" Background="#E7EFF5" >
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<local:ExecuteJunctionControl Grid.Column="0" MyNode="{Binding NodeModel}" x:Name="ExecuteJunctionControl" HorizontalAlignment="Left" Grid.RowSpan="2"/>
|
||||
<Border Grid.Column="1" BorderThickness="1" HorizontalAlignment="Stretch">
|
||||
<TextBlock Text="UI控件" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Border Grid.Row="1" x:Name="EmbedContainer" BorderBrush="Black" BorderThickness="1"
|
||||
Width="500" Height="400"/>
|
||||
|
||||
|
||||
</Grid>
|
||||
</local:NodeControlBase>
|
||||
65
Workbench/Node/View/UINodeControl.xaml.cs
Normal file
65
Workbench/Node/View/UINodeControl.xaml.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using Serein.Workbench.Node.ViewModel;
|
||||
using Serein.Workbench.Tool;
|
||||
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.Node.View
|
||||
{
|
||||
/// <summary>
|
||||
/// UINodeControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class UINodeControl : NodeControlBase, INodeJunction
|
||||
{
|
||||
public UINodeControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public UINodeControl(UINodeControlViewModel viewModel) : base(viewModel)
|
||||
{
|
||||
DataContext = viewModel;
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public JunctionControlBase ExecuteJunction => this.ExecuteJunctionControl;
|
||||
|
||||
public JunctionControlBase NextStepJunction => throw new NotImplementedException();
|
||||
|
||||
public JunctionControlBase[] ArgDataJunction => throw new NotImplementedException();
|
||||
|
||||
public JunctionControlBase ReturnDataJunction => throw new NotImplementedException();
|
||||
|
||||
|
||||
private void NodeControlBase_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
UINodeControlViewModel vm = (UINodeControlViewModel)DataContext;
|
||||
vm.InitAdapter(userControl => {
|
||||
EmbedContainer.Child = userControl;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void NodeControlBase_Initialized(object sender, EventArgs e)
|
||||
{
|
||||
UINodeControlViewModel vm = (UINodeControlViewModel)DataContext;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Workbench/Node/ViewModel/UINodeControlViewModel.cs
Normal file
40
Workbench/Node/ViewModel/UINodeControlViewModel.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.NodeFlow.Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Serein.Workbench.Node.ViewModel
|
||||
{
|
||||
public class UINodeControlViewModel : NodeControlViewModelBase
|
||||
{
|
||||
private SingleUINode NodeModel => (SingleUINode)base.NodeModel;
|
||||
//public IEmbeddedContent Adapter => NodeModel.Adapter;
|
||||
|
||||
public UINodeControlViewModel(NodeModelBase nodeModel) : base(nodeModel)
|
||||
{
|
||||
//NodeModel.Adapter.GetWindowHandle();
|
||||
}
|
||||
|
||||
public void InitAdapter(Action<UserControl> setUIDisplayHandle)
|
||||
{
|
||||
Task.Factory.StartNew(async () =>
|
||||
{
|
||||
var context = new DynamicContext(NodeModel.Env);
|
||||
await NodeModel.ExecutingAsync(context);
|
||||
if (context.NextOrientation == ConnectionInvokeType.IsSucceed
|
||||
&& NodeModel.Adapter.GetUserControl() is UserControl userControl)
|
||||
{
|
||||
NodeModel.Env.UIContextOperation.Invoke(() =>
|
||||
{
|
||||
setUIDisplayHandle.Invoke(userControl);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,14 +51,18 @@
|
||||
<ProjectReference Include="..\NodeFlow\Serein.NodeFlow.csproj" />
|
||||
<ProjectReference Include="..\Serein.Script\Serein.Script.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="WindowsFormsIntegration" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
|
||||
<PackageReference Include="Lagrange.Core" Version="0.3.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||
<PackageReference Include="ZXing.Net" Version="0.16.10" />
|
||||
<PackageReference Include="ZXing.Net.Bindings.ImageSharp" Version="0.16.15" />
|
||||
<!--<PackageReference Include="Lagrange.Core" Version="0.3.1" />-->
|
||||
<!--<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />-->
|
||||
<!--<PackageReference Include="ZXing.Net.Bindings.ImageSharp" Version="0.16.15" />-->
|
||||
|
||||
<!--<PackageReference Include="MySqlConnector" Version="2.4.0" />
|
||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.170" />
|
||||
@@ -74,4 +78,9 @@
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Views\" />
|
||||
<Folder Include="VIewModels\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
64
Workbench/Tool/EmbeddedHost.cs
Normal file
64
Workbench/Tool/EmbeddedHost.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using Serein.Library.Api;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace Serein.Workbench.Tool
|
||||
{
|
||||
|
||||
|
||||
/*public class EmbeddedHost : HwndHost
|
||||
{
|
||||
private readonly IntPtr _hwnd;
|
||||
|
||||
public EmbeddedHost(IEmbeddedContent content)
|
||||
{
|
||||
_hwnd = content.GetWindowHandle();
|
||||
}
|
||||
|
||||
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
|
||||
{
|
||||
if (_hwnd == IntPtr.Zero)
|
||||
throw new InvalidOperationException("无效的窗口句柄");
|
||||
|
||||
// 设置窗口为子窗口(必须去掉 WS_POPUP,添加 WS_CHILD)
|
||||
SetWindowLongPtr(_hwnd, GWL_STYLE, GetWindowLongPtr(_hwnd, GWL_STYLE) | WS_CHILD);
|
||||
SetParent(_hwnd, hwndParent.Handle);
|
||||
|
||||
// 让窗口填充整个区域
|
||||
SetWindowPos(_hwnd, IntPtr.Zero, 0, 0, (int)ActualWidth, (int)ActualHeight,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||
|
||||
return new HandleRef(this, _hwnd);
|
||||
}
|
||||
|
||||
protected override void DestroyWindowCore(HandleRef hwnd)
|
||||
{
|
||||
// 窗口销毁时的操作(如果需要)
|
||||
}
|
||||
|
||||
// WinAPI 导入
|
||||
private const int GWL_STYLE = -16;
|
||||
private const int WS_CHILD = 0x40000000;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern int GetWindowLongPtr(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll", SetLastError = true)]
|
||||
private static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
private const uint SWP_NOZORDER = 0x0004;
|
||||
private const uint SWP_NOACTIVATE = 0x0010;
|
||||
private const uint SWP_SHOWWINDOW = 0x0040;
|
||||
}*/
|
||||
}
|
||||
Reference in New Issue
Block a user