在GH2实现“控件”相当容易(你可以用eto很方便的更改节点gui,严格来说与在wpf,imgui上没有任何区别),using Eto.Drawing;
using Eto.Forms;
using Grasshopper2.Components;
using Grasshopper2.Doc;
using Grasshopper2.Doc.Attributes;
using Grasshopper2.Extensions;
using Grasshopper2.UI;
using Grasshopper2.UI.Canvas;
using Grasshopper2.UI.ContentBrowser;
using Grasshopper2.UI.Flex;
using Grasshopper2.UI.Primitives;
using Grasshopper2.UI.Skinning;
using GrasshopperIO;
using jianjuchanshuhua;
using ku;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Rhino.Geometry;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Brushes = Eto.Drawing.Brushes;
using Color = Eto.Drawing.Color;
using FontStyle = Eto.Drawing.FontStyle;
using Path = System.IO.Path;
using Pens = Eto.Drawing.Pens;
using PointF = Eto.Drawing.PointF;
using RectangleF = Eto.Drawing.RectangleF;
using Size = Eto.Drawing.Size;
using SystemFonts = Eto.Drawing.SystemFonts;
namespace ku
{
public class GitHubBrowser
{
public void ShowGitHubFileListWindow2(string path = “”)
{
var form = new Form
{
Title = “GitHub 仓库文件列表”,
ClientSize = new Size(600, 400)
};
var grid = new GridView();
grid.ShowHeader = true;
// 文件名列
grid.Columns.Add(new GridColumn
{
HeaderText = "文件名",
DataCell = new TextBoxCell { Binding = Binding.Property<FileItem, string>(r => r.Name) },
Expand = true
});
// 类型列
grid.Columns.Add(new GridColumn
{
HeaderText = "类型",
DataCell = new TextBoxCell { Binding = Binding.Property<FileItem, string>(r => r.Type) }
});
// 顶部返回按钮
var backButton = new Button { Text = "← 返回上一级" };
backButton.Enabled = false;
var layout = new DynamicLayout();
layout.AddRow(backButton);
layout.AddRow(grid);
form.Content = layout;
string currentPath = path;
async System.Threading.Tasks.Task LoadFiles(string relativePath)
{
string repoApiUrl = $"https://api.github.com/repos/wuq1/gh2_jzbf/contents/{relativePath}";
try
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("User-Agent", "request");
string json = await client.GetStringAsync(repoApiUrl);
var files = JArray.Parse(json);
var items = new List<FileItem>();
foreach (var file in files)
{
items.Add(new FileItem
{
Name = (string)file["name"],
Type = (string)file["type"],
DownloadUrl = (string)file["download_url"]
});
}
grid.DataStore = items;
}
backButton.Enabled = !string.IsNullOrEmpty(relativePath);
}
catch (Exception ex)
{
grid.DataStore = new List<FileItem> { new FileItem { Name = $"请求失败: {ex.Message}", Type = "" } };
backButton.Enabled = false;
}
}
// 首次加载
_ = LoadFiles(currentPath);
// 返回上一级按钮
backButton.Click += async (sender, e) =>
{
if (!string.IsNullOrEmpty(currentPath))
{
int lastSlash = currentPath.LastIndexOf('/');
currentPath = lastSlash > 0 ? currentPath.Substring(0, lastSlash) : "";
await LoadFiles(currentPath);
}
};
// 双击进入子目录 或 下载文件
grid.CellDoubleClick += async (sender, e) =>
{
if (e.Item is FileItem file)
{
if (file.Type == "dir")
{
currentPath = string.IsNullOrEmpty(currentPath) ? file.Name : $"{currentPath}/{file.Name}";
await LoadFiles(currentPath);
}
else if (file.Type == "file")
{
await DownloadFile(file, form);
}
}
};
// 右键菜单下载
var menu = new ContextMenu();
var downloadItem = new ButtonMenuItem { Text = "下载到桌面并且在参考窗口打开" };
downloadItem.Click += async (sender, e) =>
{
if (grid.SelectedItem is FileItem file && file.Type == "file")
{ await DownloadFile(file, form); }
};
menu.Items.Add(downloadItem);
grid.ContextMenu = menu;
form.Show();
}
private async System.Threading.Tasks.Task DownloadFile(FileItem file, Form parent)
{
try
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("User-Agent", "request");
string savePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
file.Name
);
byte[] bytes = await client.GetByteArrayAsync(file.DownloadUrl);
File.WriteAllBytes(savePath, bytes);
MessageBox.Show(parent, $"已下载: {file.Name}\n保存到桌面");
if (file.Name.EndsWith(".ghz", StringComparison.OrdinalIgnoreCase))
{
// 下载完成后,直接打开 GH2 文档
var use = new Use();
use.ShowFileListWindow2(savePath); // ✅ 注意这里传完整路径
}
}
}
catch (Exception ex)
{
MessageBox.Show(parent, $"下载失败: {ex.Message}");
}
}
// 数据结构
class FileItem
{
public string Name { get; set; }
public string Type { get; set; }
public string DownloadUrl { get; set; }
}
}
public class MyComponentAttributes2 : ComponentAttributes
{
private RectangleF buttonRect; // 按钮矩形
private bool pressed = false; // 按钮状态
public MyComponentAttributes2(Component owner) : base(owner) { }
protected override void LayoutBounds(Shape shape)
{
base.LayoutBounds(shape);
// 在电池底部增加 25 高的矩形区域作为按钮
float extraHeight = 25f;
buttonRect = RectangleF.FromSides(
Bounds.Left,
Bounds.Bottom,
Bounds.Right,
Bounds.Bottom + extraHeight
);
// 扩展电池整体 Bounds,包含按钮
Bounds = RectangleF.Union(Bounds, buttonRect);
}
protected override void DrawForeground(Context context, Skin skin, Capsule capsule, Shade shade)
{
base.DrawForeground(context, skin, capsule, shade);
// 按钮颜色
var fill = pressed ? Colors.DarkGray : Colors.DarkGray;
var border = Colors.Black;
// 原始按钮区域(比如在电池下面)
RectangleF buttonRect = new RectangleF(Bounds.X, Bounds.Y + 25, Bounds.Width, Bounds.Height - 26);
// 在四周都收缩 2 像素
buttonRect.Inflate(-2, -2);
// 绘制按钮
//context.Graphics.FillRectangle(fill, buttonRect);
// context.Graphics.DrawRectangle(border, buttonRect);
// 在你的电池 UI 绘制里用:
float cornerRadius = 4f;
using (var path = CreateRoundedRect(buttonRect, cornerRadius))
{
context.Graphics.FillPath(fill, path);
context.Graphics.DrawPath(Pens.Gray, path);
}
// 绘制按钮文字
var text = "一个按键";
//var font = SystemFonts.Default(10);
var font = new Eto.Drawing.Font(Eto.Drawing.SystemFont.Default, 10);
var size = context.Graphics.MeasureString(font, text);
var center = new PointF(
buttonRect.Left + (buttonRect.Width - size.Width) / 2,
buttonRect.Top + (buttonRect.Height - size.Height) / 2
);
context.Graphics.DrawText(font, Colors.Black, center, text);
}
protected override Response HandleMouseDown(MouseEventArgs e)
{
RectangleF clickRect = buttonRect;
clickRect.Inflate(-2, -2); // 同步缩小
if (buttonRect.Contains(e.Location))
{
pressed = !pressed; // 切换按钮状态
// 弹出窗口
/*
var form = new Eto.Forms.Form
{
Title = "按钮窗口",
ClientSize = new Eto.Drawing.Size(200, 100)
};
form.Content = new Eto.Forms.Label
{
Text = "你点击了按钮!",
VerticalAlignment = Eto.Forms.VerticalAlignment.Center,
TextAlignment = Eto.Forms.TextAlignment.Center // 水平居中
};
form.Show();*/
//
// ShowFileListWindow();
// GitHubBrowser b = new GitHubBrowser();
// b.ShowGitHubFileListWindow2();
Use use = new Use();
use.ShowListWindow();
Owner.Document?.Solution.DelayedExpire(Owner); // 刷新电池
return Response.Handled;
}
return base.HandleMouseDown(e);
}
//绘制窗口
GraphicsPath CreateRoundedRect(RectangleF rect, float radius)
{
var path = new GraphicsPath();
float x = rect.X;
float y = rect.Y;
float w = rect.Width;
float h = rect.Height;
float r = radius;
// 左上角
path.AddArc(x, y, r * 2, r * 2, 180, 90);
// 右上角
path.AddArc(x + w - 2 * r, y, r * 2, r * 2, 270, 90);
// 右下角
path.AddArc(x + w - 2 * r, y + h - 2 * r, r * 2, r * 2, 0, 90);
// 左下角
path.AddArc(x, y + h - 2 * r, r * 2, r * 2, 90, 90);
path.CloseFigure();
return path;
}
private Color Lerp(Color a, Color b, float t)
{
float r = a.R + (b.R - a.R) * t;
float g = a.G + (b.G - a.G) * t;
float bl = a.B + (b.B - a.B) * t;
float al = a.A + (b.A - a.A) * t;
return new Color(r, g, bl, al);
}
}
public class Use
{
public void ShowFileListWindow2(string path)
{
var form = new Form
{
Title = "GH2 FileUtility 测试",
ClientSize = new Size(800, 600)
};
// 创建 Canvas
var canvas = new Canvas();
// 打开 GH2 文档
//path = @"C:\Users\32035\Desktop\001.ghz";
bool ok = canvas.TryOpenDocument(path, OpenDocumentOptions.Activate);
// 获取当前文档(Canvas 内部会创建一个 Document)
Document doc = canvas.Document;
if (doc != null)
{
doc.Notes = "这是一个测试文档";
doc.Modify();
}
// 把 Canvas 加入窗口
form.Content = canvas;
// 在窗口关闭时清理
form.Closed += (sender, e) =>
{
if (canvas.Document != null)
{
// 关闭文档释放资源
canvas.Document.Close();
Rhino.RhinoDoc.ActiveDoc.Views.Redraw();
}
// 清空 Canvas
//canvas.Document = null;
};
// 显示窗口
form.Show();
}
public void ShowListWindow()
{
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
string filePath = Path.Combine(desktop, "information.json");
Information info;
if (File.Exists(filePath))
{
try
{
string json = File.ReadAllText(filePath);
info = JsonConvert.DeserializeObject<Information>(json) ?? new Information();
}
catch
{
info = new Information();
}
}
else
{
info = new Information();
}
var form = new Form
{
Title = "编辑信息",
ClientSize = new Size(400, 350)
};
// 文本框
var nameBox = new TextBox { Text = info.name ?? "" };
var pathBox = new TextBox { Text = info.path ?? "" };
var layersBox = new TextBox { Text = info.NumberOfLayers.ToString() };
var heightBox = new TextBox { Text = info.CeilingHeight.ToString() };
var wallBox = new TextBox { Text = info.ThickWall.ToString() };
Action syncToInfo = () =>
{
info.name = nameBox.Text;
info.path = pathBox.Text;
if (int.TryParse(layersBox.Text, out int layers)) info.NumberOfLayers = layers;
if (double.TryParse(heightBox.Text, out double height)) info.CeilingHeight = height;
if (double.TryParse(wallBox.Text, out double wall)) info.ThickWall = wall;
};
var saveBtn = new Button { Text = "保存", Height = 25 };
var importBtn = new Button { Text = "导入", Height = 25 };
var saveAsBtn = new Button { Text = "另存为", Height = 25 };
saveBtn.Click += (s, e) =>
{
syncToInfo();
string json = JsonConvert.SerializeObject(info, Formatting.Indented);
File.WriteAllText(filePath, json);
MessageBox.Show($"信息已保存到:\n{filePath}");
form.Close(); // 关闭窗口
};
importBtn.Click += (s, e) =>
{
var openDialog = new OpenFileDialog
{
Title = "选择信息文件",
Filters = { new FileFilter("JSON 文件", ".json") }
};
if (openDialog.ShowDialog(form) == DialogResult.Ok)
{
try
{
string json = File.ReadAllText(openDialog.FileName);
var loaded = JsonConvert.DeserializeObject<Information>(json);
if (loaded != null)
{
info = loaded;
nameBox.Text = info.name ?? "";
pathBox.Text = info.path ?? "";
layersBox.Text = info.NumberOfLayers.ToString();
heightBox.Text = info.CeilingHeight.ToString();
wallBox.Text = info.ThickWall.ToString();
MessageBox.Show("导入成功!");
form.Close(); // 关闭窗口
}
}
catch (Exception ex)
{
MessageBox.Show("导入失败: " + ex.Message);
}
}
};
saveAsBtn.Click += (s, e) =>
{
syncToInfo();
var saveDialog = new SaveFileDialog
{
Title = "另存为",
Filters = { new FileFilter("JSON 文件", ".json") },
FileName = "information.json"
};
if (saveDialog.ShowDialog(form) == DialogResult.Ok)
{
string json = JsonConvert.SerializeObject(info, Formatting.Indented);
File.WriteAllText(saveDialog.FileName, json);
MessageBox.Show($"文件已保存到:\n{saveDialog.FileName}");
form.Close(); // 关闭窗口
}
};
// 布局
var layout = new TableLayout
{
Padding = 10,
Spacing = new Size(5, 5)
};
layout.Rows.Add(new TableRow(new Label { Text = "名称:" }, nameBox));
layout.Rows.Add(new TableRow(new Label { Text = "地址:" }, pathBox));
layout.Rows.Add(new TableRow(new Label { Text = "层数:" }, layersBox));
layout.Rows.Add(new TableRow(new Label { Text = "层高:" }, heightBox));
layout.Rows.Add(new TableRow(new Label { Text = "墙厚:" }, wallBox));
// 按钮横向居中,单独一行
layout.Rows.Add(new TableRow(
null,
new StackLayout
{
Orientation = Orientation.Horizontal,
HorizontalContentAlignment = HorizontalAlignment.Center,
Spacing = 10,
Items = { saveBtn, importBtn, saveAsBtn }
}
));
form.Content = new Scrollable { Content = layout };
form.Show();
}
}
}一个简单的例子.只需要在节点cs中添加protected override IAttributes CreateAttributes()
{
return new MyComponentAttributes2(this);
}