做个简单的电缆统计表格【持续更新】
本帖最后由 枫叶棋语 于 2023-12-24 11:19 编辑原理: 用System.Windows.Forms 做界面,表格使用的DataGridView。
1. 表格的功能包括点击表头排序,点击数据行,CAD的亮显对应图形,包括类似Excel 的Ctrl+D,Ctrl+R以及自定义Ctrl+V事件;
2. 这次没有使用DataTable 作为数据源,而是使用自定义类,及BindingList 作为数据源,表格也支持手动输入数据,水平长度以及总长度设置不允许用户输入;
3. 按钮中有保存,加载,是将数据源转为Json文件,后期考虑添加缓存机制,保存或关闭窗口进行用户提示;以及保存历史数据记录;
4. 按钮中添加新行以及删除整行,进行表格行添加或删除操作;
5. 起点,重点编号支持在文本框写入编号,填充到选定行对应数据列;
6.绑定曲线按钮支持修改数据行要绑定的曲线;
7.查找曲线支持选择CAD中的曲线,在列表中显示对应的数据行;
8. 添加曲线支持Cad中选择图元,并将数据记录到表格
本帖最后由 枫叶棋语 于 2023-12-26 14:38 编辑
import json
import os
import typing
import clr
import System
from pycad.runtime import *
from pycad.system import *
import re, csv
clr.AddReference("System.Windows.Forms")
clr.AddReference("System.Drawing")
from System.Drawing import *
from System.Windows.Forms import *
from System.ComponentModel import BindingList
class CurveData:
def __init__(self, curve_id: int = 0) -> None:
super().__init__()
self.doc: Document = acap.Application.DocumentManager.MdiActiveDocument
self.db = self.doc.Database
self.句柄 = curve_id
self.起点 = ""
self.终点 = ""
self.回路 = ""
self.功率 = 0
self.电压 = "380V"
self.型号规格 = ""
self.电缆拼数 = 1
self.垂直长度 = 0
self.余量 = 3
self.备注 = ""
def to_dict(self):
return {
"起点": self.起点,
"终点": self.终点,
"句柄": self.句柄,
"回路": self.回路,
"功率": self.功率,
"电压": self.电压,
"型号规格": self.型号规格,
"电缆拼数": self.电缆拼数,
"垂直长度": self.垂直长度,
"余量": self.余量,
"备注": self.备注,
}
@staticmethod
def from_dict(data: dict):
curvedata = CurveData(data.get("句柄", 0))
curvedata.起点 = data.get("起点", "")
curvedata.终点 = data.get("终点", "")
curvedata.回路 = data.get("回路", "")
curvedata.功率 = data.get("功率", 0)
curvedata.型号规格 = data.get("型号规格", "")
curvedata.电缆拼数 = data.get("电缆拼数", 1)
curvedata.垂直长度 = data.get("垂直长度", 0)
curvedata.余量 = data.get("余量", 0)
curvedata.备注 = data.get("备注", "")
return curvedata
@property
def ObjectId(self) -> ObjectId:
if self.句柄 == 0 or self.句柄 == None or self.句柄 == "" or self.句柄 == System.DBNull.Value:
return "None"
else:
return self.db.GetObjectId(False, Handle(self.句柄), 0)
@property
def 水平长度(self):
if self.ObjectId == "None":
return 0
elif self.ObjectId.IsNull:
self.句柄 = 0
return 0
elif self.ObjectId.ObjectClass.DxfName not in ["LINE", "POLYLINE", "LWPOLYLINE"]:
return 0
else:
return float(f"{self.ObjectId.Length / 1000:0.2f}")
@property
def 总长度(self):
return (self.水平长度 + self.垂直长度 + self.余量) * self.电缆拼数
class DataPanel(Panel):
def __init__(self):
super().__init__()
self.ColumnsTags = ["句柄", "起点", "终点", "回路", "功率", "型号规格", "水平长度", "垂直长度", "余量", "电缆拼数", "总长度", "备注"]
self.datagrid = DataGridView()
self.reverse = False
self.Initial()
def CreateColmns(self, column_name, ReadOnly: bool):
column = DataGridViewTextBoxColumn()
column.Name = column_name
column.DataPropertyName = column_name# 数据属性名
column.HeaderText = column_name# 表头文字
column.ReadOnly = ReadOnly
column.HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter
self.datagrid.Columns.Add(column)
return column
def AddRowIndex(self):
for i, row in enumerate(self.datagrid.Rows):
row.HeaderCell.Value = str(i)
def Initial(self):
for tag in self.ColumnsTags:
if tag in ["水平长度", "总长度"]:
column = self.CreateColmns(tag, True)
column.DefaultCellStyle.Format = "N2"
elif tag in ["功率", "余量", "电缆拼数"]:
column = self.CreateColmns(tag, False)
column.DefaultCellStyle.Format = "N0"
else:
self.CreateColmns(tag, False)
self.data_objects = BindingList()
self.binding_source = BindingSource(self.data_objects, None)
self.datagrid.DataSource = self.binding_source
self.display_Column = {}
for i in range(self.datagrid.Columns.Count):
self.display_Column = i
self.reverse = False
self.datagrid.DataError += self.DataError
self.datagrid.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableAlwaysIncludeHeaderText
self.datagrid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCells
self.datagrid.Dock = DockStyle.Fill
self.datagrid.AllowUserToAddRows = True
self.datagrid.AllowUserToDeleteRows = True
self.datagrid.AllowUserToResizeRows = True
self.datagrid.MultiSelect = True
self.datagrid.ReadOnly = False
self.datagrid.RowHeadersVisible = True
self.datagrid.AutoGenerateColumns = False
self.datagrid.RowHeadersVisible = True
self.datagrid.AllowUserToOrderColumns = True
self.datagrid.RowHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight
self.datagrid.RowHeadersWidth = 50
self.datagrid.SelectionMode = DataGridViewSelectionMode.CellSelect
self.datagrid.BorderStyle = BorderStyle.FixedSingle
self.datagrid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill
self.datagrid.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter
self.Controls.Add(self.datagrid)
self.datagrid.ColumnHeaderMouseClick += self.On_ColumnHeaderMouseClick
self.datagrid.CellValueChanged += self.On_CellValueChanged
self.datagrid.KeyDown += self.On_keydown
self.datagrid.ColumnDisplayIndexChanged += self.On_ColumnDisplayIndexChanged
def DataError(self, s, e: DataGridViewDataErrorEventArgs):
"""处理数据类型错误"""
e.Cancel = True
prinf(f"{e.RowIndex}行,{e.ColumnIndex}列,输入格式错误")
return
def On_ColumnDisplayIndexChanged(self, sender, e):
"""处理手动调整列顺序事件,并更新显示列与数据列匹配字典"""
for i in range(self.datagrid.Columns.Count):
self.display_Column.DisplayIndex] = i
def Op_Clip(self):
"""处理剪贴板文字,变成数组"""
if Clipboard.ContainsText():
pasted_text = Clipboard.GetText()
rows = csv.reader(pasted_text.splitlines(), delimiter="\t")
data =
return data
else:
return None
def On_keydown(self, s, e: KeyEventArgs):
"""按键事件"""
selectedCells: typing.Collection = self.datagrid.SelectedCells
if selectedCells.Count > 0:
col_display_indexes = sorted({cell.OwningColumn.DisplayIndex for cell in selectedCells})
row_indexes = sorted({cell.RowIndex for cell in selectedCells})
if e.KeyCode == Keys.D and e.Control:
"""处理ctrl+D快捷键"""
for col in col_display_indexes:
col_cells =
col_cells.sort(key=lambda cell: cell.RowIndex)
value_col0 = col_cells.Value
for cell in col_cells:
cell.Value = value_col0
if e.KeyCode == Keys.R and e.Control:
"""处理ctrl+R快捷键"""
for row in row_indexes:
row_cells =
row_cells.sort(key=lambda cell: cell.OwningColumn.DisplayIndex)
value_row0 = row_cells.Value
for cell in row_cells:
cell.Value = value_row0
if e.KeyCode == Keys.Delete:
"""处理删除快捷键"""
for cell in selectedCells:
cell.Value = ""
if e.KeyCode == Keys.V and e.Control:
clipdata = self.Op_Clip()
columns: DataGridViewColumnCollection = self.datagrid.Columns
if clipdata:
if len(selectedCells) == 1:
selected_cell: DataGridViewTextBoxCell = selectedCells
row_index0 = selected_cell.RowIndex
col_index0 = selected_cell.OwningColumn.DisplayIndex
for i, row in enumerate(clipdata):
for j, col in enumerate(row):
if row_index0 + i < self.datagrid.Rows.Count and col_index0 + j < self.datagrid.Columns.Count:
col_index = self.display_Column
self.datagrid.Rows.Cells.Value = col
if len(selectedCells) > 1:
row_index_min = min(row_indexes)
col_index_min = min(col_display_indexes)
row_index_max = max(row_indexes)
col_index_max = max(col_display_indexes)
if (col_index_max - col_index_min + 1) * (row_index_max - row_index_min + 1) != len(selectedCells):
aleernt_message = "此选择无效,粘贴区域为完整连续的范围"
MessageBox.Show(aleernt_message, "IronPycad", MessageBoxButtons.OK, MessageBoxIcon.Error)
else:
for i, row in enumerate(clipdata):
for j, col in enumerate(row):
if row_index_min + i <= row_index_max and col_index_min + j <= col_index_max:
col_index = self.display_Column
self.datagrid.Rows.Cells.Value = col
def On_CellValueChanged(self, sender, e: DataGridViewCellEventArgs):
"""处理单元格值改变事件"""
self.binding_source.ResetItem(e.RowIndex)
def On_ColumnHeaderMouseClick(self, sender, e: DataGridViewCellMouseEventArgs):
"""处理表头点击事件,实现排序"""
column_name = self.datagrid.Columns.HeaderText
self.data_objects = BindingList(
sorted(self.data_objects, key=lambda x: getattr(x, column_name), reverse=self.reverse)
)
self.binding_source = BindingSource(self.data_objects, None)
self.datagrid.DataSource = self.binding_source
self.AddRowIndex()
self.reverse = not self.reverse
class CunstomButton(Button):
def __init__(self, text):
super().__init__()
self.Text = text
self.Size = Size(100, 25)
self.Font = Font("Arial", 8)
self.Dock = DockStyle.Top
class CustomTextBox(TextBox):
def __init__(self):
super().__init__()
self.Size = Size(100, 25)
self.Font = Font("Arial", 8)
self.Dock = DockStyle.Top
class OpPanel(Panel):
def __init__(self):
super().__init__()
self.tag_textboex = CustomTextBox()
self.add_btn = CunstomButton("添加曲线")
self.load_btn = CunstomButton("加载数据")
self.save_btn = CunstomButton("保存数据")
self.newrow_btn = CunstomButton("添加新行")
self.delrow_btn = CunstomButton("删除整行")
self.SerialStart_btn = CunstomButton("起点编号")
self.SerialEnd_btn = CunstomButton("终点编号")
self.BindingCurve_btn = CunstomButton("绑定曲线")
self.FindData_btn = CunstomButton("查找曲线")
self.FindDatas_btn = CunstomButton("批量查线")
self.Notes_btn = CunstomButton("批量备注")
self.Controls.Add(self.Notes_btn)
self.Controls.Add(self.FindDatas_btn)
self.Controls.Add(self.add_btn)
self.Controls.Add(self.FindData_btn)
self.Controls.Add(self.BindingCurve_btn)
self.Controls.Add(self.SerialEnd_btn)
self.Controls.Add(self.SerialStart_btn)
self.Controls.Add(self.tag_textboex)
self.Controls.Add(self.newrow_btn)
self.Controls.Add(self.delrow_btn)
self.Controls.Add(self.load_btn)
self.Controls.Add(self.save_btn)
class ListPanel(Form):
def __init__(self):
super().__init__()
self.doc: Document = acap.Application.DocumentManager.MdiActiveDocument
self.jsonname = os.path.splitext(self.doc.Name) + ".json"
self.InitForm()
def ReBindData(self, datalist):
handles = {dataobject.句柄 for dataobject in self.datapanel.data_objects if dataobject.句柄 != 0}
for data in datalist:
if data.句柄 not in handles:
self.datapanel.data_objects.Add(data)
self.datapanel.binding_source = BindingSource(self.datapanel.data_objects, None)
self.datapanel.datagrid.DataSource = self.datapanel.binding_source
def InitForm(self):
self.db = self.doc.Database
self.Name = self.doc.Name
self.Text = os.path.basename(self.Name)
self.Dock = DockStyle.Fill
self.Size = Size(1000, 600)
self.splitContainer = SplitContainer()
self.splitContainer.Dock = DockStyle.Fill
self.splitContainer.Location = Point(0, 0)
self.datapanel = DataPanel()
self.datapanel.Dock = DockStyle.Fill
self.datapanel.datagrid.SelectionChanged += self.On_SelectionChanged
self.oppanel = OpPanel()
self.oppanel.Dock = DockStyle.Fill
self.oppanel.add_btn.Click += self.On_Add_Click
self.oppanel.save_btn.Click += self.On_Save_Click
self.oppanel.load_btn.Click += self.On_Load_Click
self.oppanel.newrow_btn.Click += self.On_NewRow_Click
self.oppanel.delrow_btn.Click += self.On_DelRow_Click
self.oppanel.SerialStart_btn.Click += self.On_SerialStart_Click
self.oppanel.SerialEnd_btn.Click += self.On_SerialEnd_Click
self.oppanel.BindingCurve_btn.Click += self.On_BindingCurve_Click
self.oppanel.FindData_btn.Click += self.On_FindData_Click
self.oppanel.FindDatas_btn.Click += self.On_FindDatas_Click
self.oppanel.Notes_btn.Click += self.On_Notes_Click
self.splitContainer.Panel1.Controls.Add(self.datapanel)
self.splitContainer.Panel2.Controls.Add(self.oppanel)
self.Controls.Add(self.splitContainer)
self.Load += self.ListForm_Load
self.SizeChanged += self.ListForm_SizeChanged
def On_Add_Click(self, sender, e):
self.oppanel.add_btn.Enabled = False
self.doc.Window.Focus()
res = ssget(":A", TV(0, "LINE,*POLYLINE"))
if not res.ok:
self.oppanel.add_btn.Enabled = True
return
ids: typing.Collection = tuple(res)
datalist =
self.ReBindData(datalist)
self.datapanel.AddRowIndex()
self.oppanel.add_btn.Enabled = True
def ListForm_Load(self, sender, e):
self.splitContainer.SplitterDistance = self.Width - 100
def ListForm_SizeChanged(self, sender, e):
if self.WindowState != FormWindowState.Minimized:
self.splitContainer.SplitterDistance = self.Width - 100
def GetObjectId(self, curve_id: int) -> ObjectId:
if curve_id == System.DBNull.Value:
prinf("句柄需要为整数")
return
else:
return self.db.GetObjectId(False, Handle(curve_id), 0)
def On_SelectionChanged(self, s, e):
if self.datapanel.datagrid.SelectedCells.Count > 0:
Utils.SetFocusToDwgView()
cells = self.datapanel.datagrid.SelectedCells
row_indexes = {cell.RowIndex for cell in cells if cell.Value != System.DBNull.Value}
handels = [
self.datapanel.data_objects.句柄
for rowindex in row_indexes
if self.datapanel.data_objects.句柄 != 0
]
try:
objids =
sssetfirst(tuple(objids))
except:
prinf("Error: 非法句柄")
self.datapanel.datagrid.Focus()
def On_Save_Click(self, sender, e):
datalist =
json_str = json.dumps(datalist, indent=4, ensure_ascii=False)
with open(self.jsonname, "w", encoding="utf-8") as f:
f.write(json_str)
def On_Load_Click(self, sender, e):
dialog = OpenFileDialog()
dialog.Filter = "json文件(*.json)|*.json"
dialog.InitialDirectory = os.path.dirname(self.jsonname)
dialog.Multiselect = False
if dialog.ShowDialog() == DialogResult.OK:
filename = dialog.FileName
with open(filename, "r", encoding="utf-8") as f:
datalist: typing.List = json.load(f)
self.ReBindData()
self.datapanel.AddRowIndex()
def On_NewRow_Click(self, sender, e):
self.ReBindData()
self.datapanel.AddRowIndex()
def On_DelRow_Click(self, sender, e):
selectedCells = self.datapanel.datagrid.SelectedCells
rows = {cell.RowIndex for cell in selectedCells}
for row_index in sorted(rows, reverse=True):
self.datapanel.datagrid.Rows.RemoveAt(row_index)
def On_SerialStart_Click(self, sender, e):
selectedCells = self.datapanel.datagrid.SelectedCells
rows = {cell.RowIndex for cell in selectedCells}
for row_index in rows:
self.datapanel.data_objects.起点 = self.oppanel.tag_textboex.Text
self.datapanel.binding_source.ResetItem(row_index)
def On_SerialEnd_Click(self, sender, e):
selectedCells = self.datapanel.datagrid.SelectedCells
rows = {cell.RowIndex for cell in selectedCells}
for row_index in rows:
self.datapanel.data_objects.终点 = self.oppanel.tag_textboex.Text
self.datapanel.binding_source.ResetItem(row_index)
def On_BindingCurve_Click(self, sender, e):
self.oppanel.BindingCurve_btn.Enabled = False
self.doc.Window.Focus()
rowindex = self.datapanel.datagrid.SelectedCells.RowIndex
res = entsel("\n选择要绑定的曲线")
if not res.ok:
self.oppanel.BindingCurve_btn.Enabled = True
return
self.oppanel.BindingCurve_btn.Enabled = True
objid = res.ObjectId
if objid.ObjectClass.DxfName not in ["LINE", "POLYLINE", "LWPOLYLINE"]:
prinf("请选择直线或多段线")
else:
self.datapanel.data_objects.句柄 = int(objid.Handle.Value)
self.datapanel.binding_source.ResetItem(rowindex)
def On_FindData_Click(self, sender, e):
self.datapanel.datagrid.SelectionChanged -= self.On_SelectionChanged# 禁用选择更改事件
self.oppanel.FindData_btn.Enabled = False
self.doc.Window.Focus()
res = entsel("\n选择要查询的曲线")
if not res.ok:
self.oppanel.FindData_btn.Enabled = True
self.datapanel.datagrid.SelectionChanged += self.On_SelectionChanged# 启用选择更改事件
return
self.oppanel.FindData_btn.Enabled = True
objid = res.ObjectId
if objid.ObjectClass.DxfName not in ["LINE", "POLYLINE", "LWPOLYLINE"]:
prinf("请选择直线或多段线")
else:
curve_id = int(objid.Handle.Value)
for row_index in range(self.datapanel.data_objects.Count):
if self.datapanel.data_objects.句柄 == curve_id:
self.datapanel.datagrid.ClearSelection()
self.datapanel.datagrid.Rows.Selected = True
for cell_index in range(self.datapanel.datagrid.ColumnCount):
self.datapanel.datagrid.Rows.Cells.Selected = True
self.datapanel.datagrid.FirstDisplayedScrollingRowIndex = row_index
self.datapanel.datagrid.SelectionChanged += self.On_SelectionChanged# 启用选择更改事件
def On_FindDatas_Click(self, sender, e):
self.datapanel.datagrid.SelectionChanged -= self.On_SelectionChanged# 禁用选择更改事件
self.oppanel.FindDatas_btn.Enabled = False
self.doc.Window.Focus()
res = ssget(":A", TV(0, "LINE,*POLYLINE"))
if not res.ok:
self.oppanel.FindDatas_btn.Enabled = True
self.datapanel.datagrid.SelectionChanged += self.On_SelectionChanged# 启用选择更改事件
return
ids: typing.List = tuple(res)
handles =
self.datapanel.datagrid.ClearSelection()
for curve_id in handles:
for row_index in range(self.datapanel.data_objects.Count):
if self.datapanel.data_objects.句柄 == curve_id:
for cell_index in range(self.datapanel.datagrid.ColumnCount):
self.datapanel.datagrid.Rows.Cells.Selected = True
self.oppanel.FindDatas_btn.Enabled = True
self.datapanel.datagrid.SelectionChanged += self.On_SelectionChanged# 启用选择更改事件
def On_Notes_Click(self, sender, e):
selectedCells = self.datapanel.datagrid.SelectedCells
rows = {cell.RowIndex for cell in selectedCells}
for row_index in rows:
self.datapanel.data_objects.备注 = self.oppanel.tag_textboex.Text
self.datapanel.binding_source.ResetItem(row_index)
@Command(flags=CommandFlags.UsePickSet)
def SG(doc: Document):
db = doc.Database
listform = ListPanel()
acap.Application.ShowModelessDialog(owner=doc.Window.Handle, formToShow=listform, persistSizeAndPosition=False)
guohq 发表于 2023-12-23 14:52
建议楼主说明一下代码的功能、对数据的要求,CAD版本等信息,不然贴一堆代码,让别人看起来也很费劲。
如果你不用pycad,你是看不懂的
1028695446 发表于 2023-12-23 23:02
感觉还是lisp代码看起来简洁些
这里的主要代码是界面和事件代码,其实CAD交互就几句 厉害,高手 这还简单吗?对我来说太难了。 wangsr 发表于 2023-12-22 18:19
这还简单吗?对我来说太难了。
很简单的,一点不难 建议楼主说明一下代码的功能、对数据的要求,CAD版本等信息,不然贴一堆代码,让别人看起来也很费劲。 枫叶棋语 发表于 2023-12-23 15:27
如果你不用pycad,你是看不懂的
什么是PYCAD能普及下最好谢谢 wangsr 发表于 2023-12-23 17:27
什么是PYCAD能普及下最好谢谢
用python写的CAD插件
感觉还是lisp代码看起来简洁些
页:
[1]
2