diff --git a/Ramitta/Database/PostgreSQL.cs b/Ramitta/Database/PostgreSQL.cs index dabc074..ed732d0 100644 --- a/Ramitta/Database/PostgreSQL.cs +++ b/Ramitta/Database/PostgreSQL.cs @@ -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"; + } + } + + db = new NpgsqlConnection(connString); + db.Open(); } - // 执行查询操作,返回查询结果 - public async Task>> ExecuteQueryAsync(string query, Dictionary parameters = null) + // 创建表:根据表名和字段定义创建表 + public void CreateTable(string tableName, Dictionary 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 GetAllTableNames() + { + List tableNames = new List(); + + 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 columns) + { + foreach (var column in columns) + { + AddColumn(tableName, column.Key, column.Value); + } + } + + // 插入数据:向指定表插入一条记录 + // 参数: tableName - 表名 + // 参数: columnValues - 字段和对应值的字典 + // 例如: InsertData("Users", new Dictionary { {"Name", "John"}, {"Age", 30} }); + public void InsertData(string tableName, Dictionary 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> SelectData(string query, Dictionary parameters = null) { var result = new List>(); - using (var conn = new NpgsqlConnection(_connectionString)) + using (var cmd = new NpgsqlCommand(query, db)) { - try + // 添加查询参数(如果有的话) + if (parameters != null) { - await conn.OpenAsync(); - Debug.WriteLine("Database connection established."); - - using (var cmd = new NpgsqlCommand(query, conn)) + foreach (var kvp in parameters) { - if (parameters != null) - { - foreach (var param in parameters) - { - cmd.Parameters.AddWithValue(param.Key, param.Value ?? DBNull.Value); - } - } - - using (var reader = await cmd.ExecuteReaderAsync()) - { - while (await reader.ReadAsync()) - { - var row = new Dictionary(); - for (int i = 0; i < reader.FieldCount; i++) - { - row[reader.GetName(i)] = await reader.IsDBNullAsync(i) ? null : reader.GetValue(i); - } - result.Add(row); - } - } + cmd.Parameters.AddWithValue("@" + kvp.Key, kvp.Value ?? DBNull.Value); } - - Debug.WriteLine($"Query executed: {query}"); } - catch (Exception ex) + + using (var reader = cmd.ExecuteReader()) { - Debug.WriteLine($"Error executing query: {ex.Message}"); + while (reader.Read()) + { + var row = new Dictionary(); + for (int i = 0; i < reader.FieldCount; i++) + { + row[reader.GetName(i)] = reader.GetValue(i); + } + result.Add(row); + } } } return result; } - // 执行插入、更新、删除操作 - public async Task ExecuteNonQueryAsync(string query, Dictionary parameters = null) + // 更新数据:根据条件更新指定表中的记录 + public void UpdateData(string tableName, Dictionary 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)) - { - if (parameters != null) - { - foreach (var param in parameters) - { - cmd.Parameters.AddWithValue(param.Key, param.Value ?? DBNull.Value); - } - } - - var rowsAffected = await cmd.ExecuteNonQueryAsync(); - Debug.WriteLine($"Executed query: {query}, Rows affected: {rowsAffected}"); - return rowsAffected; - } + transactionActions.Invoke(); // 执行多个操作 + transaction.Commit(); // 提交事务 } - catch (Exception ex) + catch (Exception) { - Debug.WriteLine($"Error executing non-query: {ex.Message}"); - return -1; + transaction.Rollback(); // 回滚事务 + throw; } } } - // 执行插入操作,返回生成的主键 - public async Task ExecuteInsertAsync(string query, Dictionary parameters = null, string returnColumn = "id") + // 删除所有表 + public void DropAllTables() { - using (var conn = new NpgsqlConnection(_connectionString)) + // 获取所有表名 + string getTablesQuery = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public';"; + var tables = SelectData(getTablesQuery); + + foreach (var table in tables) { - try + string tableName = table["table_name"].ToString(); + string dropTableQuery = $"DROP TABLE IF EXISTS \"{tableName}\" CASCADE;"; + + using (var cmd = new NpgsqlCommand(dropTableQuery, db)) { - 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); - } - } - - 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) - { - Debug.WriteLine($"Error executing insert: {ex.Message}"); - return -1; + cmd.ExecuteNonQuery(); } } } - // 执行事务操作 - public async Task ExecuteTransactionAsync(List queries, List> parametersList) + public int ExecuteBatchInsert(string tableName, List columns, string columnsStr, List> batch, int batchIndex) { - using (var conn = new NpgsqlConnection(_connectionString)) + var valueParams = new List(); + var parameters = new Dictionary(); + + for (int i = 0; i < batch.Count; i++) { - 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); - } - } + var paramNames = columns.Select(col => $"@p{batchIndex}_{i}_{col}").ToList(); + valueParams.Add($"({string.Join(", ", paramNames)})"); - await cmd.ExecuteNonQueryAsync(); - } - } - - await transaction.CommitAsync(); - Debug.WriteLine("Transaction committed."); - return true; - } - } - catch (Exception ex) + foreach (var col in columns) { - Debug.WriteLine($"Error executing transaction: {ex.Message}"); - return false; + 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); + } + + int result = cmd.ExecuteNonQuery(); + transaction.Commit(); + return result; + } + } + + // 修改主方法 + public int BulkInsert(string tableName, List> dataList) + { + 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++; + } + + return insertedCount; + } + + // 释放资源,关闭数据库连接 + public void Dispose() + { + if (!disposed) + { + if (db != null && db.State == ConnectionState.Open) + { + db.Close(); + db.Dispose(); + } + disposed = true; + } + GC.SuppressFinalize(this); + } + + // 析构函数,调用Dispose释放资源 + ~PostgreSQL() + { + Dispose(); + } + + // PostgreSQL特有的方法:创建连接字符串的便捷方法 + public static string BuildConnectionString(string host, string database, string username, string password, int port = 5432) + { + return $"Host={host};Port={port};Database={database};Username={username};Password={password}"; } } -} +} \ No newline at end of file diff --git a/Ramitta/Database/SQLite.cs b/Ramitta/Database/SQLite.cs index ade526f..dfd1e56 100644 --- a/Ramitta/Database/SQLite.cs +++ b/Ramitta/Database/SQLite.cs @@ -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 columns, string columnsStr, List> batch, int batchIndex) + { + var valueParams = new List(); + var parameters = new Dictionary(); + + 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> 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() diff --git a/Ramitta/Excel.cs b/Ramitta/Excel.cs index 851e898..b08f44c 100644 --- a/Ramitta/Excel.cs +++ b/Ramitta/Excel.cs @@ -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>(); // 打开 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>(); // 打开 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; + } + + /// + /// 获取单元格的字符串值,如果是公式则先计算 + /// + /// NPOI单元格对象 + /// 单元格的值字符串,如果为空返回null + 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; + } + } } diff --git a/Ramitta/Ramitta.cs b/Ramitta/Ramitta.cs index b226e24..370b963 100644 --- a/Ramitta/Ramitta.cs +++ b/Ramitta/Ramitta.cs @@ -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еIJȷ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 + { + // ʹNPOIExcelļ + 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 diff --git a/Ramitta/Ramitta.csproj b/Ramitta/Ramitta.csproj index 8b1f3e0..6de4be7 100644 --- a/Ramitta/Ramitta.csproj +++ b/Ramitta/Ramitta.csproj @@ -1,14 +1,17 @@ - net8.0-windows + net8.0-windows7.0 enable true enable + 7.0 + + diff --git a/Ramitta/RegistryOperations.cs b/Ramitta/RegistryOperations.cs new file mode 100644 index 0000000..db06b68 --- /dev/null +++ b/Ramitta/RegistryOperations.cs @@ -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 +{ + /// + /// 注册表操作类 - 提供完整的注册表增删改查功能 + /// + public static class RegistryOperations + { + #region 枚举定义 + + /// + /// 注册表值类型 + /// + public enum RegValueKind + { + String = 1, + ExpandString = 2, + Binary = 3, + DWord = 4, + MultiString = 7, + QWord = 11 + } + + /// + /// 注册表根键类型 + /// + public enum RegRootKey + { + ClassesRoot, + CurrentUser, + LocalMachine, + Users, + CurrentConfig + } + + #endregion + + #region 私有方法 + + /// + /// 解析完整注册表路径 + /// + 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]}") + }; + } + + /// + /// 获取RegistryKey根键 + /// + 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}") + }; + } + + /// + /// 转换值类型 + /// + 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 查询操作 + + /// + /// 检查注册表项是否存在 + /// + /// 完整注册表路径 + /// 是否存在 + 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; + } + } + + /// + /// 检查注册表值是否存在 + /// + /// 完整注册表路径 + /// 值名称 + /// 是否存在 + 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; + } + } + + /// + /// 读取注册表值 + /// + /// 完整注册表路径 + /// 值名称 + /// 默认值 + /// 读取到的值,如果不存在返回默认值 + 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); + } + } + + /// + /// 读取字符串值 + /// + public static string ReadString(string fullPath, string valueName, string defaultValue = "") + { + return ReadValue(fullPath, valueName, defaultValue) as string ?? defaultValue; + } + + /// + /// 读取整数值(DWORD) + /// + public static int ReadDWord(string fullPath, string valueName, int defaultValue = 0) + { + object value = ReadValue(fullPath, valueName, defaultValue); + return value is int intValue ? intValue : defaultValue; + } + + /// + /// 读取长整数值(QWORD) + /// + public static long ReadQWord(string fullPath, string valueName, long defaultValue = 0) + { + object value = ReadValue(fullPath, valueName, defaultValue); + return value is long longValue ? longValue : defaultValue; + } + + /// + /// 读取二进制值 + /// + public static byte[] ReadBinary(string fullPath, string valueName, byte[] defaultValue = null) + { + object value = ReadValue(fullPath, valueName, defaultValue); + return value as byte[] ?? defaultValue ?? Array.Empty(); + } + + /// + /// 读取多字符串值 + /// + public static string[] ReadMultiString(string fullPath, string valueName, string[] defaultValue = null) + { + object value = ReadValue(fullPath, valueName, defaultValue); + return value as string[] ?? defaultValue ?? Array.Empty(); + } + + /// + /// 获取所有子键名称 + /// + 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(); + } + catch (Exception ex) + { + throw new InvalidOperationException($"获取子键列表失败: {ex.Message}", ex); + } + } + + /// + /// 获取所有值名称 + /// + 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(); + } + catch (Exception ex) + { + throw new InvalidOperationException($"获取值名称列表失败: {ex.Message}", ex); + } + } + + /// + /// 获取值的数据类型 + /// + 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 新增操作 + + /// + /// 创建注册表项 + /// + /// 完整注册表路径 + /// 是否创建成功 + 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); + } + } + + /// + /// 写入注册表值 + /// + /// 完整注册表路径 + /// 值名称 + /// 值数据 + /// 值类型 + 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); + } + } + + /// + /// 写入字符串值 + /// + public static void WriteString(string fullPath, string valueName, string value) + { + WriteValue(fullPath, valueName, value, RegValueKind.String); + } + + /// + /// 写入扩展字符串值 + /// + public static void WriteExpandString(string fullPath, string valueName, string value) + { + WriteValue(fullPath, valueName, value, RegValueKind.ExpandString); + } + + /// + /// 写入DWORD值 + /// + public static void WriteDWord(string fullPath, string valueName, int value) + { + WriteValue(fullPath, valueName, value, RegValueKind.DWord); + } + + /// + /// 写入QWORD值 + /// + public static void WriteQWord(string fullPath, string valueName, long value) + { + WriteValue(fullPath, valueName, value, RegValueKind.QWord); + } + + /// + /// 写入二进制值 + /// + public static void WriteBinary(string fullPath, string valueName, byte[] value) + { + WriteValue(fullPath, valueName, value, RegValueKind.Binary); + } + + /// + /// 写入多字符串值 + /// + public static void WriteMultiString(string fullPath, string valueName, string[] value) + { + WriteValue(fullPath, valueName, value, RegValueKind.MultiString); + } + + #endregion + + #region 修改操作 + + /// + /// 重命名注册表值 + /// + /// 完整注册表路径 + /// 原值名称 + /// 新值名称 + 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 删除操作 + + /// + /// 删除注册表值 + /// + /// 完整注册表路径 + /// 值名称 + /// 是否删除成功 + 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); + } + } + + /// + /// 删除注册表项 + /// + /// 完整注册表路径 + /// 是否递归删除所有子项 + /// 是否删除成功 + 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 高级功能 + + /// + /// 递归获取所有子键 + /// + public static List GetAllSubKeys(string fullPath, int maxDepth = 10) + { + List results = new List(); + GetAllSubKeysRecursive(fullPath, results, 0, maxDepth); + return results; + } + + private static void GetAllSubKeysRecursive(string currentPath, List 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 + { + // 忽略无权限访问的键 + } + } + + /// + /// 备份注册表项到文件 + /// + 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 + } +} \ No newline at end of file diff --git a/Ramitta/SolidWorks.cs b/Ramitta/SolidWorks.cs new file mode 100644 index 0000000..d8e8df2 --- /dev/null +++ b/Ramitta/SolidWorks.cs @@ -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 + { + + } +} diff --git a/Ramitta/winDataGrid.xaml.cs b/Ramitta/winDataGrid.xaml.cs index dd03e4b..77ebb93 100644 --- a/Ramitta/winDataGrid.xaml.cs +++ b/Ramitta/winDataGrid.xaml.cs @@ -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 AddRow() { var keys = ColumnsName.Keys.ToList(); diff --git a/Ramitta/winView3D.xaml b/Ramitta/winView3D.xaml new file mode 100644 index 0000000..960b06b --- /dev/null +++ b/Ramitta/winView3D.xaml @@ -0,0 +1,10 @@ + + + diff --git a/Ramitta/winView3D.xaml.cs b/Ramitta/winView3D.xaml.cs new file mode 100644 index 0000000..5ce9690 --- /dev/null +++ b/Ramitta/winView3D.xaml.cs @@ -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 + }; + + // 添加到MainGrid(XAML中定义的Grid) + MainGrid.Children.Add(_viewport3D); + // 注册名称 + RegisterName(name, _viewport3D); + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to initialize 3D view: {ex.Message}", ex); + } + } + /// + /// 在两点之间绘制一条3D直线 + /// + /// 起点 + /// 终点 + /// 线条颜色(默认红色) + /// 线条粗细(默认2.0) + /// 创建的线条可视化对象 + 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(); + } + } +} \ No newline at end of file