加入若干pgsql的接口

This commit is contained in:
2025-12-13 16:33:41 +08:00
parent 97337f31ce
commit f9db3c7226
10 changed files with 1526 additions and 131 deletions

View File

@@ -1,171 +1,380 @@
using Npgsql;
using System.Diagnostics;
using Newtonsoft.Json.Linq;
using Npgsql;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SQLite;
using System.Linq;
namespace Ramitta
{
public partial class PostgreSql
public class PostgreSQL : IDisposable
{
private readonly string _connectionString;
private NpgsqlConnection db;
private bool disposed = false;
public PostgreSql(string connectionString)
// 构造函数,初始化数据库连接
public PostgreSQL(string connectionString, bool readOnly = false)
{
_connectionString = connectionString;
var connString = connectionString;
if (readOnly)
{
if (!connString.Contains("CommandTimeout"))
{
connString += ";CommandTimeout=0";
}
}
// 执行查询操作,返回查询结果
public async Task<List<Dictionary<string, object>>> ExecuteQueryAsync(string query, Dictionary<string, object> parameters = null)
db = new NpgsqlConnection(connString);
db.Open();
}
// 创建表:根据表名和字段定义创建表
public void CreateTable(string tableName, Dictionary<string, string> columns)
{
// 构建列定义的字符串
var columnsDefinition = string.Join(", ", columns.Select(c => $"\"{c.Key}\" {MapDataType(c.Value)}"));
string createTableQuery = $"CREATE TABLE IF NOT EXISTS \"{tableName}\" ({columnsDefinition});";
using (var cmd = new NpgsqlCommand(createTableQuery, db))
{
cmd.ExecuteNonQuery();
}
}
// 数据类型映射SQLite到PostgreSQL
private string MapDataType(string sqliteType)
{
return sqliteType.ToUpper() switch
{
"INTEGER" => "INTEGER",
"TEXT" => "TEXT",
"REAL" => "REAL",
"BLOB" => "BYTEA",
"NUMERIC" => "NUMERIC",
"BOOLEAN" => "BOOLEAN",
"DATE" => "DATE",
"DATETIME" => "TIMESTAMP",
"TIMESTAMP" => "TIMESTAMP",
_ => "TEXT" // 默认映射为TEXT
};
}
// 获取数据库中所有表名
public List<string> GetAllTableNames()
{
List<string> tableNames = new List<string>();
string query = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';";
using (var cmd = new NpgsqlCommand(query, db))
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
string tableName = reader.GetString(0);
tableNames.Add(tableName);
}
}
return tableNames;
}
// 向已存在的表中添加新列
public void AddColumn(string tableName, string columnName, string columnType)
{
// 检查表是否存在
if (!TableExists(tableName))
{
throw new ArgumentException($"表 '{tableName}' 不存在");
}
// 检查列是否已存在
if (ColumnExists(tableName, columnName))
{
Console.WriteLine($"列 '{columnName}' 在表 '{tableName}' 中已存在");
return;
}
// 构建添加列的SQL语句
string addColumnQuery = $"ALTER TABLE \"{tableName}\" ADD COLUMN \"{columnName}\" {MapDataType(columnType)};";
using (var cmd = new NpgsqlCommand(addColumnQuery, db))
{
cmd.ExecuteNonQuery();
}
Console.WriteLine($"已向表 '{tableName}' 添加列 '{columnName}'");
}
// 检查表是否存在
private bool TableExists(string tableName)
{
string query = "SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name = @tableName;";
using (var cmd = new NpgsqlCommand(query, db))
{
cmd.Parameters.AddWithValue("@tableName", tableName);
var result = cmd.ExecuteScalar();
return Convert.ToInt64(result) > 0;
}
}
// 检查列是否已存在
private bool ColumnExists(string tableName, string columnName)
{
string query = "SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = @tableName;";
using (var cmd = new NpgsqlCommand(query, db))
{
cmd.Parameters.AddWithValue("@tableName", tableName);
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
if (reader["column_name"].ToString().Equals(columnName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
}
return false;
}
// 批量添加多个列
public void AddColumns(string tableName, Dictionary<string, string> columns)
{
foreach (var column in columns)
{
AddColumn(tableName, column.Key, column.Value);
}
}
// 插入数据:向指定表插入一条记录
// 参数: tableName - 表名
// 参数: columnValues - 字段和对应值的字典
// 例如: InsertData("Users", new Dictionary<string, object> { {"Name", "John"}, {"Age", 30} });
public void InsertData(string tableName, Dictionary<string, object> columnValues)
{
// 构建列名字符串(添加引号防止保留关键字冲突)
var columns = string.Join(", ", columnValues.Keys.Select(k => $"\"{k}\""));
// 判断是否需要 JSON 转换,并构建参数占位符
var parameters = columnValues.Keys.Select(k =>
{
var value = columnValues[k];
if (value is JObject || value is JArray) // 判断是否是 JObject 或 JArray
{
return $"@{k}::json";
}
return $"@{k}";
});
// 构建插入语句
string insertQuery = $"INSERT INTO \"{tableName}\" ({columns}) VALUES ({string.Join(", ", parameters)})";
// 使用 NpgsqlCommand 而不是 SQLiteCommand
using (var cmd = new NpgsqlCommand(insertQuery, db))
{
foreach (var kvp in columnValues)
{
if (kvp.Value is JObject jObj)
{
// JObject 转为字符串
cmd.Parameters.AddWithValue("@" + kvp.Key, jObj.ToString());
}
else if (kvp.Value is JArray jArray)
{
// JArray 转为字符串
cmd.Parameters.AddWithValue("@" + kvp.Key, jArray.ToString());
}
else
{
// 处理 null 值
cmd.Parameters.AddWithValue("@" + kvp.Key, kvp.Value ?? DBNull.Value);
}
}
cmd.ExecuteNonQuery();
}
}
// 查询数据:执行任意查询语句并返回结果
public List<Dictionary<string, object>> SelectData(string query, Dictionary<string, object> parameters = null)
{
var result = new List<Dictionary<string, object>>();
using (var conn = new NpgsqlConnection(_connectionString))
{
try
{
await conn.OpenAsync();
Debug.WriteLine("Database connection established.");
using (var cmd = new NpgsqlCommand(query, conn))
using (var cmd = new NpgsqlCommand(query, db))
{
// 添加查询参数(如果有的话)
if (parameters != null)
{
foreach (var param in parameters)
foreach (var kvp in parameters)
{
cmd.Parameters.AddWithValue(param.Key, param.Value ?? DBNull.Value);
cmd.Parameters.AddWithValue("@" + kvp.Key, kvp.Value ?? DBNull.Value);
}
}
using (var reader = await cmd.ExecuteReaderAsync())
using (var reader = cmd.ExecuteReader())
{
while (await reader.ReadAsync())
while (reader.Read())
{
var row = new Dictionary<string, object>();
for (int i = 0; i < reader.FieldCount; i++)
{
row[reader.GetName(i)] = await reader.IsDBNullAsync(i) ? null : reader.GetValue(i);
row[reader.GetName(i)] = reader.GetValue(i);
}
result.Add(row);
}
}
}
Debug.WriteLine($"Query executed: {query}");
}
catch (Exception ex)
{
Debug.WriteLine($"Error executing query: {ex.Message}");
}
}
return result;
}
// 执行插入、更新、删除操作
public async Task<int> ExecuteNonQueryAsync(string query, Dictionary<string, object> parameters = null)
// 更新数据:根据条件更新指定表中的记录
public void UpdateData(string tableName, Dictionary<string, object> columnValues, string condition)
{
using (var conn = new NpgsqlConnection(_connectionString))
// 构建SET子句
var setClause = string.Join(", ", columnValues.Keys.Select(k => $"\"{k}\" = @{k}"));
string updateQuery = $"UPDATE \"{tableName}\" SET {setClause} WHERE {condition}";
using (var cmd = new NpgsqlCommand(updateQuery, db))
{
foreach (var kvp in columnValues)
{
cmd.Parameters.AddWithValue("@" + kvp.Key, kvp.Value ?? DBNull.Value);
}
cmd.ExecuteNonQuery();
}
}
// 删除数据:根据条件删除指定表中的记录
public void DeleteData(string tableName, string condition)
{
string deleteQuery = $"DELETE FROM \"{tableName}\" WHERE {condition}";
using (var cmd = new NpgsqlCommand(deleteQuery, db))
{
cmd.ExecuteNonQuery();
}
}
// 支持事务操作:允许在同一个事务中执行多个操作
public void ExecuteTransaction(Action transactionActions)
{
using (var transaction = db.BeginTransaction())
{
try
{
await conn.OpenAsync();
Debug.WriteLine("Database connection established.");
using (var cmd = new NpgsqlCommand(query, conn))
transactionActions.Invoke(); // 执行多个操作
transaction.Commit(); // 提交事务
}
catch (Exception)
{
if (parameters != null)
transaction.Rollback(); // 回滚事务
throw;
}
}
}
// 删除所有表
public void DropAllTables()
{
// 获取所有表名
string getTablesQuery = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';";
var tables = SelectData(getTablesQuery);
foreach (var table in tables)
{
string tableName = table["table_name"].ToString();
string dropTableQuery = $"DROP TABLE IF EXISTS \"{tableName}\" CASCADE;";
using (var cmd = new NpgsqlCommand(dropTableQuery, db))
{
cmd.ExecuteNonQuery();
}
}
}
public int ExecuteBatchInsert(string tableName, List<string> columns, string columnsStr, List<Dictionary<string, object>> batch, int batchIndex)
{
var valueParams = new List<string>();
var parameters = new Dictionary<string, object>();
for (int i = 0; i < batch.Count; i++)
{
var paramNames = columns.Select(col => $"@p{batchIndex}_{i}_{col}").ToList();
valueParams.Add($"({string.Join(", ", paramNames)})");
foreach (var col in columns)
{
parameters[$"p{batchIndex}_{i}_{col}"] = batch[i][col] ?? DBNull.Value;
}
}
string insertQuery = $"INSERT INTO \"{tableName}\" ({columnsStr}) VALUES {string.Join(", ", valueParams)}";
using (var transaction = db.BeginTransaction())
using (var cmd = new NpgsqlCommand(insertQuery, db, transaction))
{
foreach (var param in parameters)
{
cmd.Parameters.AddWithValue(param.Key, param.Value ?? DBNull.Value);
cmd.Parameters.AddWithValue(param.Key, param.Value);
}
int result = cmd.ExecuteNonQuery();
transaction.Commit();
return result;
}
}
var rowsAffected = await cmd.ExecuteNonQueryAsync();
Debug.WriteLine($"Executed query: {query}, Rows affected: {rowsAffected}");
return rowsAffected;
}
}
catch (Exception ex)
// 修改主方法
public int BulkInsert(string tableName, List<Dictionary<string, object>> dataList)
{
Debug.WriteLine($"Error executing non-query: {ex.Message}");
return -1;
}
}
if (dataList == null || dataList.Count == 0)
return 0;
int insertedCount = 0;
var columns = dataList[0].Keys.ToList();
var columnsStr = string.Join(", ", columns.Select(c => $"\"{c}\""));
int batchSize = 500;
int batchIndex = 0;
for (int i = 0; i < dataList.Count; i += batchSize)
{
var batch = dataList.Skip(i).Take(batchSize).ToList();
insertedCount += ExecuteBatchInsert(tableName, columns, columnsStr, batch, batchIndex);
batchIndex++;
}
// 执行插入操作,返回生成的主键
public async Task<int> ExecuteInsertAsync(string query, Dictionary<string, object> parameters = null, string returnColumn = "id")
{
using (var conn = new NpgsqlConnection(_connectionString))
{
try
{
await conn.OpenAsync();
Debug.WriteLine("Database connection established.");
using (var cmd = new NpgsqlCommand(query, conn))
{
if (parameters != null)
{
foreach (var param in parameters)
{
cmd.Parameters.AddWithValue(param.Key, param.Value ?? DBNull.Value);
}
return insertedCount;
}
cmd.CommandText += $" RETURNING {returnColumn};";
var result = await cmd.ExecuteScalarAsync();
Debug.WriteLine($"Executed insert, inserted ID: {result}");
return result != null ? Convert.ToInt32(result) : -1;
}
}
catch (Exception ex)
// 释放资源,关闭数据库连接
public void Dispose()
{
Debug.WriteLine($"Error executing insert: {ex.Message}");
return -1;
if (!disposed)
{
if (db != null && db.State == ConnectionState.Open)
{
db.Close();
db.Dispose();
}
disposed = true;
}
GC.SuppressFinalize(this);
}
// 执行事务操作
public async Task<bool> ExecuteTransactionAsync(List<string> queries, List<Dictionary<string, object>> parametersList)
// 析构函数调用Dispose释放资源
~PostgreSQL()
{
using (var conn = new NpgsqlConnection(_connectionString))
{
try
{
await conn.OpenAsync();
Debug.WriteLine("Database connection established.");
using (var transaction = await conn.BeginTransactionAsync())
{
for (int i = 0; i < queries.Count; i++)
{
using (var cmd = new NpgsqlCommand(queries[i], conn, transaction))
{
var parameters = parametersList[i];
if (parameters != null)
{
foreach (var param in parameters)
{
cmd.Parameters.AddWithValue(param.Key, param.Value ?? DBNull.Value);
}
Dispose();
}
await cmd.ExecuteNonQueryAsync();
}
}
await transaction.CommitAsync();
Debug.WriteLine("Transaction committed.");
return true;
}
}
catch (Exception ex)
// PostgreSQL特有的方法创建连接字符串的便捷方法
public static string BuildConnectionString(string host, string database, string username, string password, int port = 5432)
{
Debug.WriteLine($"Error executing transaction: {ex.Message}");
return false;
}
}
return $"Host={host};Port={port};Database={database};Username={username};Password={password}";
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Data;
using Newtonsoft.Json.Linq;
using System.Data;
using System.Data.SQLite;
using static NPOI.HSSF.Util.HSSFColor;
@@ -263,6 +264,60 @@ namespace Ramitta
}
}
public int ExecuteBatchInsert(string tableName, List<string> columns, string columnsStr, List<Dictionary<string, object>> batch, int batchIndex)
{
var valueParams = new List<string>();
var parameters = new Dictionary<string, object>();
for (int i = 0; i < batch.Count; i++)
{
var paramNames = columns.Select(col => $"@p{batchIndex}_{i}_{col}").ToList();
valueParams.Add($"({string.Join(", ", paramNames)})");
foreach (var col in columns)
{
parameters[$"p{batchIndex}_{i}_{col}"] = batch[i][col] ?? DBNull.Value;
}
}
string insertQuery = $"INSERT INTO {tableName} ({columnsStr}) VALUES {string.Join(", ", valueParams)}";
using (var transaction = db.BeginTransaction())
using (var cmd = new SQLiteCommand(insertQuery, db, transaction))
{
foreach (var param in parameters)
{
cmd.Parameters.AddWithValue(param.Key, param.Value);
}
int result = cmd.ExecuteNonQuery();
transaction.Commit();
return result;
}
}
// 修改主方法
public int BulkInsert(string tableName, List<Dictionary<string, object>> dataList)
{
if (dataList == null || dataList.Count == 0)
return 0;
int insertedCount = 0;
var columns = dataList[0].Keys.ToList();
var columnsStr = string.Join(", ", columns);
int batchSize = 500;
int batchIndex = 0; // 添加批次索引
for (int i = 0; i < dataList.Count; i += batchSize)
{
var batch = dataList.Skip(i).Take(batchSize).ToList();
insertedCount += ExecuteBatchInsert(tableName, columns, columnsStr, batch, batchIndex);
batchIndex++; // 递增批次索引
}
return insertedCount;
}
// 释放资源,关闭数据库连接
// 确保数据库连接在对象销毁时被正确关闭
public void Dispose()

View File

@@ -1,6 +1,9 @@
using NPOI.SS.UserModel;
using NPOI.SS.Formula.Functions;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System.DirectoryServices;
using System.IO;
using System.Windows.Controls;
namespace Ramitta.lib
{
@@ -15,7 +18,7 @@ namespace Ramitta.lib
var result = new List<Dictionary<string, string>>();
// 打开 Excel 文件
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
var workbook = new XSSFWorkbook(fs);
ISheet sheet = null;
@@ -87,7 +90,7 @@ namespace Ramitta.lib
var result = new Dictionary<string, List<string>>();
// 打开 Excel 文件
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
var workbook = new XSSFWorkbook(fs);
ISheet sheet = null;
@@ -186,6 +189,90 @@ namespace Ramitta.lib
return cell;
}
public static ICell? getRowCell(IRow row, object cellIndex)
{
int actualCellIndex = 0;
if (cellIndex is int intIndex)
{
// 如果输入是数字,直接使用
actualCellIndex = intIndex;
}
else if (cellIndex is string strIndex)
{
actualCellIndex = ColToIndex(strIndex);
}
else
{
return null;
}
if (row == null) return null;
ICell cell = row.GetCell(actualCellIndex);
if (cell == null) return null;
return cell;
}
/// <summary>
/// 获取单元格的字符串值,如果是公式则先计算
/// </summary>
/// <param name="cell">NPOI单元格对象</param>
/// <returns>单元格的值字符串如果为空返回null</returns>
public static string? getFormula(ICell cell)
{
if (cell == null)
return null;
try
{
// 如果是公式单元格
if (cell.CellType == CellType.Formula)
{
// 获取公式计算器
if (cell.Sheet?.Workbook != null)
{
var evaluator = cell.Sheet.Workbook.GetCreationHelper().CreateFormulaEvaluator();
var cellValue = evaluator.Evaluate(cell);
// 将计算结果转为字符串
if (cellValue == null) return null;
switch (cellValue.CellType)
{
case CellType.String:
return cellValue.StringValue?.Trim();
case CellType.Numeric:
return cellValue.NumberValue.ToString();
case CellType.Boolean:
return cellValue.BooleanValue.ToString();
case CellType.Error:
return "#ERROR";
case CellType.Blank:
return null;
default:
return cellValue.FormatAsString()?.Trim();
}
}
else
{
// 没有计算器,获取单元格的字符串表示
return cell.ToString()?.Trim();
}
}
else
{
// 非公式单元格,直接获取字符串表示
return cell.ToString()?.Trim();
}
}
catch (Exception)
{
// 计算出错时返回空
return null;
}
}
public static String? getRowCellStr(ISheet sheet, int rowIndex, object cellIndex)
{
@@ -207,6 +294,149 @@ namespace Ramitta.lib
var cellValue = getRowCell(sheet, rowIndex, actualCellIndex)?.ToString();
return string.IsNullOrWhiteSpace(cellValue) ? null : cellValue;
}
public static String? getRowCellStr(IRow row, object cellIndex,bool Formula=false)
{
int actualCellIndex = 0;
if (cellIndex is int intIndex)
{
// 如果输入是数字,直接使用
actualCellIndex = intIndex;
}
else if (cellIndex is string strIndex)
{
actualCellIndex = ColToIndex(strIndex);
}
else
{
return null;
}
if (Formula) {
return getFormula(getRowCell(row, actualCellIndex));
} else {
var cellValue = getRowCell(row, actualCellIndex)?.ToString();
return string.IsNullOrWhiteSpace(cellValue) ? null : cellValue;
}
}
public static float? getRowCellFloat(ISheet sheet, int rowIndex, object cellIndex)
{
int actualCellIndex = 0;
if (cellIndex is int intIndex)
{
actualCellIndex = intIndex;
}
else if (cellIndex is string strIndex)
{
actualCellIndex = ColToIndex(strIndex);
}
else
{
return null;
}
var cell = getRowCell(sheet, rowIndex, actualCellIndex);
if (cell == null) return null;
// 获取单元格的实际值(不是公式本身)
string cellValue;
if (cell.CellType == CellType.Formula)
{
// 如果是公式,获取公式计算后的值
cellValue = cell.NumericCellValue.ToString();
}
else
{
cellValue = cell.ToString();
}
// 解析为float
if (float.TryParse(cellValue, out float result))
{
return result;
}
return null;
}
public static float? getRowCellFloat(IRow row, object cellIndex)
{
int actualCellIndex = 0;
if (cellIndex is int intIndex)
{
actualCellIndex = intIndex;
}
else if (cellIndex is string strIndex)
{
actualCellIndex = ColToIndex(strIndex);
}
else
{
return null;
}
var cell = getRowCell(row, actualCellIndex);
if (cell == null) return null;
// 获取单元格的实际值(不是公式本身)
string cellValue;
if (cell.CellType == CellType.Formula)
{
// 如果是公式,获取公式计算后的值
cellValue = cell.NumericCellValue.ToString();
}
else
{
cellValue = cell.ToString();
}
// 解析为float
if (float.TryParse(cellValue, out float result))
{
return result;
}
return null;
}
public static double? getRowCellDouble(IRow row, object cellIndex)
{
int actualCellIndex = 0;
if (cellIndex is int intIndex)
{
actualCellIndex = intIndex;
}
else if (cellIndex is string strIndex)
{
actualCellIndex = ColToIndex(strIndex);
}
else
{
return null;
}
var cell = getRowCell(row, actualCellIndex);
if (cell == null) return null;
// 获取单元格的实际值(不是公式本身)
string cellValue;
if (cell.CellType == CellType.Formula)
{
// 如果是公式,获取公式计算后的值
cellValue = cell.NumericCellValue.ToString();
}
else
{
cellValue = cell.ToString();
}
if (double.TryParse(cellValue, out double result))
{
return result;
}
return null;
}
// 简短版本
// 列名字转为列号
@@ -214,5 +444,49 @@ namespace Ramitta.lib
{
return col.ToUpper().Aggregate(0, (cur, ch) => cur * 26 + (ch - 'A'));
}
public static ICellStyle CreateStyle(
IWorkbook workbook,
bool Border = true,
short? fontSize = null,
bool isBold = false,
bool isItalic = false,
string fontName = "宋体",
HorizontalAlignment hAlign = HorizontalAlignment.Left, // 水平对齐
VerticalAlignment vAlign = VerticalAlignment.Center) // 垂直对齐) // 新增:字体名称,默认为宋体
{
ICellStyle style = workbook.CreateCellStyle();
if (Border)
{
// 设置边框
style.BorderTop = BorderStyle.Thin;
style.BorderBottom = BorderStyle.Thin;
style.BorderLeft = BorderStyle.Thin;
style.BorderRight = BorderStyle.Thin;
}
// 设置对齐方式
style.Alignment = hAlign;
style.VerticalAlignment = vAlign;
// 创建字体
IFont font = workbook.CreateFont();
// 设置字体名称
font.FontName = fontName;
// 设置字号
if (fontSize.HasValue)
font.FontHeightInPoints = fontSize.Value;
// 设置粗体斜体
font.IsBold = isBold;
font.IsItalic = isItalic;
style.SetFont(font);
return style;
}
}
}

View File

@@ -1,3 +1,4 @@
using NPOI.SS.UserModel;
using System.Diagnostics;
using System.IO;
using System.Net;
@@ -42,6 +43,7 @@ namespace Ramitta.lib
obj.UpdateLayout();
// 处理Dispatcher队列中的其他挂起的操作确保UI更新
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { }));
});
}
public static SolidColorBrush GenerateRandomColor(SolidColorBrush color = null, bool? requireLightColor = null)
@@ -221,6 +223,38 @@ namespace Ramitta.lib
}
return pngEncoder;
}
public static void (string selectedFile, ComboBox combox)
{
try
{
// 使用NPOI打开Excel文件
using (FileStream fileStream = new FileStream(selectedFile, FileMode.Open, FileAccess.Read,FileShare.ReadWrite))
{
IWorkbook workbook = WorkbookFactory.Create(fileStream);
// 清空现有项
combox.Items.Clear();
// 遍历所有sheet并添加到下拉框
for (int i = 0; i < workbook.NumberOfSheets; i++)
{
string sheetName = workbook.GetSheetName(i);
combox.Items.Add(sheetName);
}
// 自动选择最后一个sheet
if (combox != null && combox.Items != null && combox.Items.Count > 0)
{
combox.SelectedIndex = combox.Items.Count - 1;
}
}
}
catch (Exception ex)
{
//DebugBar(Debugtag, $"读取Excel文件时出错{ex.Message}", 错误红色);
}
}
#endregion
#region

View File

@@ -1,14 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<TargetFramework>net8.0-windows7.0</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<ImplicitUsings>enable</ImplicitUsings>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="HelixToolkit" Version="3.1.2" />
<PackageReference Include="HelixToolkit.Wpf" Version="3.1.2" />
<PackageReference Include="Neo4j.Driver" Version="5.28.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Npgsql" Version="9.0.4" />

View File

@@ -0,0 +1,583 @@
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;
namespace Ramitta.lib
{
/// <summary>
/// 注册表操作类 - 提供完整的注册表增删改查功能
/// </summary>
public static class RegistryOperations
{
#region
/// <summary>
/// 注册表值类型
/// </summary>
public enum RegValueKind
{
String = 1,
ExpandString = 2,
Binary = 3,
DWord = 4,
MultiString = 7,
QWord = 11
}
/// <summary>
/// 注册表根键类型
/// </summary>
public enum RegRootKey
{
ClassesRoot,
CurrentUser,
LocalMachine,
Users,
CurrentConfig
}
#endregion
#region
/// <summary>
/// 解析完整注册表路径
/// </summary>
private static (RegRootKey rootKey, string relativePath) ParseFullPath(string fullPath)
{
if (string.IsNullOrWhiteSpace(fullPath))
throw new ArgumentException("注册表路径不能为空");
string[] parts = fullPath.Split('\\');
if (parts.Length < 1)
throw new ArgumentException("无效的注册表路径格式");
string rootStr = parts[0].ToUpper();
string relativePath = parts.Length > 1 ? string.Join("\\", parts.Skip(1)) : "";
return rootStr switch
{
"HKEY_CLASSES_ROOT" or "HKCR" => (RegRootKey.ClassesRoot, relativePath),
"HKEY_CURRENT_USER" or "HKCU" => (RegRootKey.CurrentUser, relativePath),
"HKEY_LOCAL_MACHINE" or "HKLM" => (RegRootKey.LocalMachine, relativePath),
"HKEY_USERS" or "HKU" => (RegRootKey.Users, relativePath),
"HKEY_CURRENT_CONFIG" or "HKCC" => (RegRootKey.CurrentConfig, relativePath),
_ => throw new ArgumentException($"不支持的注册表根键: {parts[0]}")
};
}
/// <summary>
/// 获取RegistryKey根键
/// </summary>
private static RegistryKey GetRootRegistryKey(RegRootKey rootKey)
{
return rootKey switch
{
RegRootKey.ClassesRoot => Registry.ClassesRoot,
RegRootKey.CurrentUser => Registry.CurrentUser,
RegRootKey.LocalMachine => Registry.LocalMachine,
RegRootKey.Users => Registry.Users,
RegRootKey.CurrentConfig => Registry.CurrentConfig,
_ => throw new ArgumentException($"不支持的根键类型: {rootKey}")
};
}
/// <summary>
/// 转换值类型
/// </summary>
private static RegistryValueKind ToRegistryValueKind(RegValueKind valueKind)
{
return valueKind switch
{
RegValueKind.String => RegistryValueKind.String,
RegValueKind.ExpandString => RegistryValueKind.ExpandString,
RegValueKind.Binary => RegistryValueKind.Binary,
RegValueKind.DWord => RegistryValueKind.DWord,
RegValueKind.MultiString => RegistryValueKind.MultiString,
RegValueKind.QWord => RegistryValueKind.QWord,
_ => RegistryValueKind.Unknown
};
}
#endregion
#region
/// <summary>
/// 检查注册表项是否存在
/// </summary>
/// <param name="fullPath">完整注册表路径</param>
/// <returns>是否存在</returns>
public static bool KeyExists(string fullPath)
{
try
{
var (rootKey, relativePath) = ParseFullPath(fullPath);
using RegistryKey root = GetRootRegistryKey(rootKey);
using RegistryKey key = root.OpenSubKey(relativePath);
return key != null;
}
catch
{
return false;
}
}
/// <summary>
/// 检查注册表值是否存在
/// </summary>
/// <param name="fullPath">完整注册表路径</param>
/// <param name="valueName">值名称</param>
/// <returns>是否存在</returns>
public static bool ValueExists(string fullPath, string valueName)
{
try
{
var (rootKey, relativePath) = ParseFullPath(fullPath);
using RegistryKey root = GetRootRegistryKey(rootKey);
using RegistryKey key = root.OpenSubKey(relativePath);
return key?.GetValue(valueName) != null;
}
catch
{
return false;
}
}
/// <summary>
/// 读取注册表值
/// </summary>
/// <param name="fullPath">完整注册表路径</param>
/// <param name="valueName">值名称</param>
/// <param name="defaultValue">默认值</param>
/// <returns>读取到的值,如果不存在返回默认值</returns>
public static object ReadValue(string fullPath, string valueName, object defaultValue = null)
{
try
{
var (rootKey, relativePath) = ParseFullPath(fullPath);
using RegistryKey root = GetRootRegistryKey(rootKey);
using RegistryKey key = root.OpenSubKey(relativePath);
return key?.GetValue(valueName, defaultValue) ?? defaultValue;
}
catch (SecurityException ex)
{
throw new UnauthorizedAccessException($"没有权限读取注册表路径: {fullPath}", ex);
}
catch (Exception ex)
{
throw new InvalidOperationException($"读取注册表值时发生错误: {ex.Message}", ex);
}
}
/// <summary>
/// 读取字符串值
/// </summary>
public static string ReadString(string fullPath, string valueName, string defaultValue = "")
{
return ReadValue(fullPath, valueName, defaultValue) as string ?? defaultValue;
}
/// <summary>
/// 读取整数值(DWORD)
/// </summary>
public static int ReadDWord(string fullPath, string valueName, int defaultValue = 0)
{
object value = ReadValue(fullPath, valueName, defaultValue);
return value is int intValue ? intValue : defaultValue;
}
/// <summary>
/// 读取长整数值(QWORD)
/// </summary>
public static long ReadQWord(string fullPath, string valueName, long defaultValue = 0)
{
object value = ReadValue(fullPath, valueName, defaultValue);
return value is long longValue ? longValue : defaultValue;
}
/// <summary>
/// 读取二进制值
/// </summary>
public static byte[] ReadBinary(string fullPath, string valueName, byte[] defaultValue = null)
{
object value = ReadValue(fullPath, valueName, defaultValue);
return value as byte[] ?? defaultValue ?? Array.Empty<byte>();
}
/// <summary>
/// 读取多字符串值
/// </summary>
public static string[] ReadMultiString(string fullPath, string valueName, string[] defaultValue = null)
{
object value = ReadValue(fullPath, valueName, defaultValue);
return value as string[] ?? defaultValue ?? Array.Empty<string>();
}
/// <summary>
/// 获取所有子键名称
/// </summary>
public static string[] GetSubKeyNames(string fullPath)
{
try
{
var (rootKey, relativePath) = ParseFullPath(fullPath);
using RegistryKey root = GetRootRegistryKey(rootKey);
using RegistryKey key = root.OpenSubKey(relativePath);
return key?.GetSubKeyNames() ?? Array.Empty<string>();
}
catch (Exception ex)
{
throw new InvalidOperationException($"获取子键列表失败: {ex.Message}", ex);
}
}
/// <summary>
/// 获取所有值名称
/// </summary>
public static string[] GetValueNames(string fullPath)
{
try
{
var (rootKey, relativePath) = ParseFullPath(fullPath);
using RegistryKey root = GetRootRegistryKey(rootKey);
using RegistryKey key = root.OpenSubKey(relativePath);
return key?.GetValueNames() ?? Array.Empty<string>();
}
catch (Exception ex)
{
throw new InvalidOperationException($"获取值名称列表失败: {ex.Message}", ex);
}
}
/// <summary>
/// 获取值的数据类型
/// </summary>
public static RegValueKind? GetValueKind(string fullPath, string valueName)
{
try
{
var (rootKey, relativePath) = ParseFullPath(fullPath);
using RegistryKey root = GetRootRegistryKey(rootKey);
using RegistryKey key = root.OpenSubKey(relativePath);
if (key == null) return null;
RegistryValueKind kind = key.GetValueKind(valueName);
return (RegValueKind)kind;
}
catch
{
return null;
}
}
#endregion
#region
/// <summary>
/// 创建注册表项
/// </summary>
/// <param name="fullPath">完整注册表路径</param>
/// <returns>是否创建成功</returns>
public static bool CreateKey(string fullPath)
{
try
{
var (rootKey, relativePath) = ParseFullPath(fullPath);
using RegistryKey root = GetRootRegistryKey(rootKey);
using RegistryKey key = root.CreateSubKey(relativePath);
return key != null;
}
catch (Exception ex)
{
throw new InvalidOperationException($"创建注册表项失败: {ex.Message}", ex);
}
}
/// <summary>
/// 写入注册表值
/// </summary>
/// <param name="fullPath">完整注册表路径</param>
/// <param name="valueName">值名称</param>
/// <param name="value">值数据</param>
/// <param name="valueKind">值类型</param>
public static void WriteValue(string fullPath, string valueName, object value, RegValueKind valueKind = RegValueKind.String)
{
try
{
var (rootKey, relativePath) = ParseFullPath(fullPath);
using RegistryKey root = GetRootRegistryKey(rootKey);
using RegistryKey key = root.CreateSubKey(relativePath);
if (key == null)
throw new InvalidOperationException($"无法创建或打开注册表项: {fullPath}");
RegistryValueKind registryValueKind = ToRegistryValueKind(valueKind);
key.SetValue(valueName, value, registryValueKind);
}
catch (SecurityException ex)
{
throw new UnauthorizedAccessException($"没有权限写入注册表路径: {fullPath}", ex);
}
catch (Exception ex)
{
throw new InvalidOperationException($"写入注册表值时发生错误: {ex.Message}", ex);
}
}
/// <summary>
/// 写入字符串值
/// </summary>
public static void WriteString(string fullPath, string valueName, string value)
{
WriteValue(fullPath, valueName, value, RegValueKind.String);
}
/// <summary>
/// 写入扩展字符串值
/// </summary>
public static void WriteExpandString(string fullPath, string valueName, string value)
{
WriteValue(fullPath, valueName, value, RegValueKind.ExpandString);
}
/// <summary>
/// 写入DWORD值
/// </summary>
public static void WriteDWord(string fullPath, string valueName, int value)
{
WriteValue(fullPath, valueName, value, RegValueKind.DWord);
}
/// <summary>
/// 写入QWORD值
/// </summary>
public static void WriteQWord(string fullPath, string valueName, long value)
{
WriteValue(fullPath, valueName, value, RegValueKind.QWord);
}
/// <summary>
/// 写入二进制值
/// </summary>
public static void WriteBinary(string fullPath, string valueName, byte[] value)
{
WriteValue(fullPath, valueName, value, RegValueKind.Binary);
}
/// <summary>
/// 写入多字符串值
/// </summary>
public static void WriteMultiString(string fullPath, string valueName, string[] value)
{
WriteValue(fullPath, valueName, value, RegValueKind.MultiString);
}
#endregion
#region
/// <summary>
/// 重命名注册表值
/// </summary>
/// <param name="fullPath">完整注册表路径</param>
/// <param name="oldValueName">原值名称</param>
/// <param name="newValueName">新值名称</param>
public static void RenameValue(string fullPath, string oldValueName, string newValueName)
{
try
{
// 读取原值
object value = ReadValue(fullPath, oldValueName);
RegValueKind? kind = GetValueKind(fullPath, oldValueName);
if (value == null || kind == null)
throw new InvalidOperationException($"原值不存在或无法读取: {oldValueName}");
// 写入新值
WriteValue(fullPath, newValueName, value, kind.Value);
// 删除原值
DeleteValue(fullPath, oldValueName);
}
catch (Exception ex)
{
throw new InvalidOperationException($"重命名注册表值失败: {ex.Message}", ex);
}
}
#endregion
#region
/// <summary>
/// 删除注册表值
/// </summary>
/// <param name="fullPath">完整注册表路径</param>
/// <param name="valueName">值名称</param>
/// <returns>是否删除成功</returns>
public static bool DeleteValue(string fullPath, string valueName)
{
try
{
var (rootKey, relativePath) = ParseFullPath(fullPath);
using RegistryKey root = GetRootRegistryKey(rootKey);
using RegistryKey key = root.OpenSubKey(relativePath, true);
if (key == null) return false;
key.DeleteValue(valueName, false);
return true;
}
catch (Exception ex)
{
throw new InvalidOperationException($"删除注册表值失败: {ex.Message}", ex);
}
}
/// <summary>
/// 删除注册表项
/// </summary>
/// <param name="fullPath">完整注册表路径</param>
/// <param name="recursive">是否递归删除所有子项</param>
/// <returns>是否删除成功</returns>
public static bool DeleteKey(string fullPath, bool recursive = true)
{
try
{
var (rootKey, relativePath) = ParseFullPath(fullPath);
using RegistryKey root = GetRootRegistryKey(rootKey);
if (recursive)
{
root.DeleteSubKeyTree(relativePath, false);
}
else
{
root.DeleteSubKey(relativePath, false);
}
return true;
}
catch (Exception ex)
{
throw new InvalidOperationException($"删除注册表项失败: {ex.Message}", ex);
}
}
#endregion
#region
/// <summary>
/// 递归获取所有子键
/// </summary>
public static List<string> GetAllSubKeys(string fullPath, int maxDepth = 10)
{
List<string> results = new List<string>();
GetAllSubKeysRecursive(fullPath, results, 0, maxDepth);
return results;
}
private static void GetAllSubKeysRecursive(string currentPath, List<string> results, int currentDepth, int maxDepth)
{
if (currentDepth >= maxDepth) return;
try
{
string[] subKeys = GetSubKeyNames(currentPath);
foreach (string subKey in subKeys)
{
string subKeyPath = $"{currentPath}\\{subKey}";
results.Add(subKeyPath);
GetAllSubKeysRecursive(subKeyPath, results, currentDepth + 1, maxDepth);
}
}
catch
{
// 忽略无权限访问的键
}
}
/// <summary>
/// 备份注册表项到文件
/// </summary>
public static async Task BackupToFile(string fullPath, string outputFilePath)
{
try
{
var (rootKey, relativePath) = ParseFullPath(fullPath);
using RegistryKey root = GetRootRegistryKey(rootKey);
using RegistryKey key = root.OpenSubKey(relativePath);
if (key == null)
throw new InvalidOperationException($"注册表项不存在: {fullPath}");
StringBuilder sb = new StringBuilder();
sb.AppendLine($"注册表备份: {fullPath}");
sb.AppendLine($"备份时间: {DateTime.Now}");
sb.AppendLine("=" + new string('=', 50));
// 备份值
string[] valueNames = key.GetValueNames();
foreach (string valueName in valueNames)
{
object value = key.GetValue(valueName);
RegistryValueKind kind = key.GetValueKind(valueName);
sb.AppendLine($"值: {valueName ?? "()"}");
sb.AppendLine($"类型: {kind}");
sb.AppendLine($"数据: {ConvertValueToString(value, kind)}");
sb.AppendLine();
}
// 备份子键结构
sb.AppendLine("子键结构:");
BackupSubKeysRecursive(key, sb, 1);
await File.WriteAllTextAsync(outputFilePath, sb.ToString(), Encoding.UTF8);
}
catch (Exception ex)
{
throw new InvalidOperationException($"备份注册表失败: {ex.Message}", ex);
}
}
private static void BackupSubKeysRecursive(RegistryKey parentKey, StringBuilder sb, int depth)
{
string[] subKeyNames = parentKey.GetSubKeyNames();
foreach (string subKeyName in subKeyNames)
{
sb.AppendLine($"{new string(' ', depth * 2)}[{subKeyName}]");
using RegistryKey subKey = parentKey.OpenSubKey(subKeyName);
if (subKey != null)
{
BackupSubKeysRecursive(subKey, sb, depth + 1);
}
}
}
private static string ConvertValueToString(object value, RegistryValueKind kind)
{
return kind switch
{
RegistryValueKind.String or RegistryValueKind.ExpandString => value as string ?? string.Empty,
RegistryValueKind.DWord => ((int)value).ToString(),
RegistryValueKind.QWord => ((long)value).ToString(),
RegistryValueKind.Binary => BitConverter.ToString((byte[])value),
RegistryValueKind.MultiString => string.Join("; ", (string[])value),
_ => value?.ToString() ?? string.Empty
};
}
#endregion
}
}

65
Ramitta/SolidWorks.cs Normal file
View File

@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Text;
using System.Threading.Tasks;
namespace Ramitta.lib
{
public static class ComInterop
{
internal const String OLEAUT32 = "oleaut32.dll";
internal const String OLE32 = "ole32.dll";
[System.Security.SecurityCritical] // auto-generated_required
public static Object GetActiveObject(String progID)
{
Object? obj = null;
Guid clsid;
// Call CLSIDFromProgIDEx first then fall back on CLSIDFromProgID if
// CLSIDFromProgIDEx doesn't exist.
try
{
CLSIDFromProgIDEx(progID, out clsid);
}
// catch
catch (Exception)
{
CLSIDFromProgID(progID, out clsid);
}
GetActiveObject(ref clsid, IntPtr.Zero, out obj);
return obj;
}
//[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]
[DllImport(OLE32, PreserveSig = false)]
[ResourceExposure(ResourceScope.None)]
[SuppressUnmanagedCodeSecurity]
[System.Security.SecurityCritical] // auto-generated
private static extern void CLSIDFromProgIDEx([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid);
//[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]
[DllImport(OLE32, PreserveSig = false)]
[ResourceExposure(ResourceScope.None)]
[SuppressUnmanagedCodeSecurity]
[System.Security.SecurityCritical] // auto-generated
private static extern void CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid);
//[DllImport(Microsoft.Win32.Win32Native.OLEAUT32, PreserveSig = false)]
[DllImport(OLEAUT32, PreserveSig = false)]
[ResourceExposure(ResourceScope.None)]
[SuppressUnmanagedCodeSecurity]
[System.Security.SecurityCritical] // auto-generated
private static extern void GetActiveObject(ref Guid rclsid, IntPtr reserved, [MarshalAs(UnmanagedType.Interface)] out Object ppunk);
}
public static class SolidWorks
{
}
}

View File

@@ -75,6 +75,7 @@ namespace Ramitta
elementFactory.SetValue(ComboBox.SelectedIndexProperty, 0);
SetBindingToProperty(elementFactory, ComboBox.ItemsSourceProperty, $"[{columnName}].ItemsSource");
SetBindingToProperty(elementFactory, ComboBox.SelectedValueProperty, $"[{columnName}].SelectedValue");
SetBindingToProperty(elementFactory, ComboBox.MaxWidthProperty, $"[{columnName}].MaxWidth");
break;
case ColumnType.Label:
elementFactory = new FrameworkElementFactory(typeof(Label));
@@ -94,9 +95,6 @@ namespace Ramitta
column.CellEditingTemplate = dataTemplate;
xDataGrid.Columns.Add(column);
}
public Dictionary<string, object> AddRow()
{
var keys = ColumnsName.Keys.ToList();

10
Ramitta/winView3D.xaml Normal file
View File

@@ -0,0 +1,10 @@
<UserControl x:Class="Ramitta.winView3D"
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:Ramitta" xmlns:h="http://helix-toolkit.org/wpf"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid x:Name="MainGrid"/>
</UserControl>

164
Ramitta/winView3D.xaml.cs Normal file
View File

@@ -0,0 +1,164 @@
using HelixToolkit.Geometry;
using HelixToolkit.Wpf;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace Ramitta
{
public partial class winView3D : UserControl
{
public HelixViewport3D _viewport3D;
public winView3D()
{
InitializeComponent();
// 默认初始化
Init("Viewport3D");
}
public void Init(string name = "Viewport3D")
{
// 检查是否已有Viewport3D
var existingViewport = FindName(name) as HelixViewport3D;
if (existingViewport != null)
{
_viewport3D = existingViewport;
return;
}
try
{
// 创建新的Viewport3D
_viewport3D = new HelixViewport3D
{
Name = name,
//Background = Brushes.White,
IsPanEnabled = true,
IsRotationEnabled = true,
IsZoomEnabled = true,
ShowCoordinateSystem = true,
ShowViewCube = true,
ZoomExtentsWhenLoaded = true
};
// 添加到MainGridXAML中定义的Grid
MainGrid.Children.Add(_viewport3D);
// 注册名称
RegisterName(name, _viewport3D);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failed to initialize 3D view: {ex.Message}", ex);
}
}
/// <summary>
/// 在两点之间绘制一条3D直线
/// </summary>
/// <param name="point1">起点</param>
/// <param name="point2">终点</param>
/// <param name="color">线条颜色(默认红色)</param>
/// <param name="thickness">线条粗细默认2.0</param>
/// <returns>创建的线条可视化对象</returns>
public LinesVisual3D DrawLineBetweenPoints( Point3D point1,
Point3D point2,
Color color = default,
double thickness = 2.0,
bool point1head=false,
Color point1color=default,
double point1radius = 0.5,
bool point2head = false,
Color point2color = default,
double point2radius = 0.5)
{
// 如果没有指定颜色,使用红色
if (color == default) color = Colors.Red;
var line = new LinesVisual3D
{
Points = new Point3DCollection { point1, point2 },
Color = color,
Thickness = thickness
};
_viewport3D.Children.Add(line);
if (point1head)
{
DrawPointAsSphere(point1, color: point1color, radius: point1radius);
}
if (point2head)
{
DrawPointAsSphere(point2, color: point2color, radius: point2radius);
}
return line;
}
public SphereVisual3D DrawPointAsSphere(Point3D point,
Color color = default,
double radius = 0.5,
int resolution = 10)
{
if (color == default) color = Colors.Green;
var sphere = new SphereVisual3D
{
Center = point,
Radius = radius,
ThetaDiv = resolution,
PhiDiv = resolution,
Material = new DiffuseMaterial(new SolidColorBrush(color))
};
_viewport3D.Children.Add(sphere);
return sphere;
}
// 其他辅助方法
public void ClearAllModels()
{
if (_viewport3D == null) return;
// 保留光源和坐标轴网格,移除其他
for (int i = _viewport3D.Children.Count - 1; i >= 0; i--)
{
var child = _viewport3D.Children[i];
if (!(child is SunLight || child is GridLinesVisual3D))
{
_viewport3D.Children.RemoveAt(i);
}
}
}
public void SetCamera(Point3D Position, Vector3D LookDirection) {
// 设置相机 - 斜向下视角
var camera = new PerspectiveCamera
{
// 从右上后方位看向结构中心,并稍微向下倾斜
// 相机位置在结构的右、上、后方,距离根据结构尺寸动态调整
Position = Position,
// 看向结构的中心点
LookDirection = LookDirection,
UpDirection = new Vector3D(0, 1, 0), // Y轴为上方向
FieldOfView = 45 // 视野角度
};
_viewport3D.Camera = camera;
_viewport3D.ZoomExtents();
}
public void ZoomToExtents()
{
_viewport3D?.ZoomExtents();
}
public void ResetView()
{
_viewport3D?.ResetCamera();
}
}
}