枫叶棋语 发表于 2023-12-22 16:33:40

做个简单的电缆统计表格【持续更新】

本帖最后由 枫叶棋语 于 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-23 15:25:49

本帖最后由 枫叶棋语 于 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)

枫叶棋语 发表于 2023-12-23 15:27:29

guohq 发表于 2023-12-23 14:52
建议楼主说明一下代码的功能、对数据的要求,CAD版本等信息,不然贴一堆代码,让别人看起来也很费劲。

如果你不用pycad,你是看不懂的

枫叶棋语 发表于 2023-12-24 11:00:47

1028695446 发表于 2023-12-23 23:02
感觉还是lisp代码看起来简洁些

这里的主要代码是界面和事件代码,其实CAD交互就几句

yuebu 发表于 2023-12-22 16:39:44

厉害,高手

wangsr 发表于 2023-12-22 18:19:05

这还简单吗?对我来说太难了。

枫叶棋语 发表于 2023-12-23 08:09:49

wangsr 发表于 2023-12-22 18:19
这还简单吗?对我来说太难了。

很简单的,一点不难

guohq 发表于 2023-12-23 14:52:07

建议楼主说明一下代码的功能、对数据的要求,CAD版本等信息,不然贴一堆代码,让别人看起来也很费劲。

wangsr 发表于 2023-12-23 17:27:34

枫叶棋语 发表于 2023-12-23 15:27
如果你不用pycad,你是看不懂的

什么是PYCAD能普及下最好谢谢

咏郡 发表于 2023-12-23 20:25:16

wangsr 发表于 2023-12-23 17:27
什么是PYCAD能普及下最好谢谢

用python写的CAD插件

1028695446 发表于 2023-12-23 23:02:13

感觉还是lisp代码看起来简洁些
页: [1] 2
查看完整版本: 做个简单的电缆统计表格【持续更新】