package ui import ( "context" "fmt" "log" "os" "strconv" "time" "github.com/ying32/govcl/vcl" "github.com/ying32/govcl/vcl/types" "github.com/ying32/govcl/vcl/types/colors" "go.bug.st/serial" "bj_power_wms_client/internal/api" "bj_power_wms_client/internal/app" "bj_power_wms_client/internal/autostart" "bj_power_wms_client/internal/config" "bj_power_wms_client/internal/serialport" ) type itemRow struct { panel *vcl.TPanel name *vcl.TLabel value *vcl.TLabel unit *vcl.TLabel } type MainForm struct { *vcl.TForm cfg config.Config apiClient *api.Client collector *app.Collector serial *serialport.Port // 模块A:顶部控制栏 portLab *vcl.TLabel portCombo *vcl.TComboBox statusLabel *vcl.TLabel connectBtn *vcl.TButton refreshBtn *vcl.TButton labU *vcl.TLabel rbMM *vcl.TRadioButton rbInch *vcl.TRadioButton autoStartChk *vcl.TCheckBox // 模块B:工单信? workOrderNoLabel *vcl.TLabel workOrderNoEdit *vcl.TEdit queryBtn *vcl.TButton typeLabel *vcl.TLabel typeCombo *vcl.TComboBox countLabel *vcl.TLabel countValue *vcl.TLabel statusLabel2 *vcl.TLabel statusValue *vcl.TLabel timeLabel *vcl.TLabel timeValue *vcl.TLabel // 模块C:仪表盘显示? rows []itemRow currentType string // 模块D:操作与数据? bottomLabel *vcl.TLabel workpieceNoEdit *vcl.TEdit filterBtn *vcl.TButton exportBtn *vcl.TButton clearBtn *vcl.TButton submitBtn *vcl.TButton histView *vcl.TListView // 定时? reconnT *vcl.TTimer // 当前工单信息 currentWorkOrder *api.WorkOrderInfo } var MainFormPtr *MainForm var injectedCfg config.Config var injectedAppName string func InitForm(c config.Config, appName string) { injectedCfg = c injectedAppName = appName } const ( baseW int32 = 1280 baseH int32 = 720 minFont int32 = 20 maxFont int32 = 96 panelH int32 = 180 gapX int32 = 8 valSize int32 = 28 nameSize int32 = 16 unitSize int32 = 16 topRowH int32 = 44 ) var squareItemDefs = []string{"?弧面半径 B"} var circleItemDefs = []string{"?球面直径 A", "?球面轮廓?B", "?球面位置 C"} func (f *MainForm) OnFormCreate(sender vcl.IObject) { f.cfg = injectedCfg f.apiClient = api.NewClient(f.cfg.APIURL) f.collector = app.NewCollector(squareItemDefs) f.SetCaption("7315-2 千分尺采?) f.SetWidth(1280) f.SetHeight(720) f.SetPosition(types.PoScreenCenter) f.SetWindowState(types.WsMaximized) f.SetOnResize(f.onResize) f.initModuleA() f.initModuleB() f.initModuleC("square") f.initModuleD() f.reconnT = vcl.NewTimer(f) f.reconnT.SetInterval(3000) f.reconnT.SetOnTimer(func(s vcl.IObject) { f.tryConnectSerial() }) f.reconnT.SetEnabled(false) f.relayout() f.refreshPortList() f.tryConnectSerial() f.onFilter(nil) } func (f *MainForm) initModuleA() { f.portLab = vcl.NewLabel(f) f.portLab.SetParent(f) f.portLab.SetLeft(16) f.portLab.SetTop(12) f.portLab.SetCaption("串口:") f.portLab.Font().SetSize(14) f.portCombo = vcl.NewComboBox(f) f.portCombo.SetParent(f) f.portCombo.SetLeft(72) f.portCombo.SetTop(8) f.portCombo.SetWidth(140) f.portCombo.SetHeight(topRowH) f.portCombo.SetStyle(types.CsDropDownList) f.portCombo.Font().SetSize(14) f.statusLabel = vcl.NewLabel(f) f.statusLabel.SetParent(f) f.statusLabel.SetLeft(220) f.statusLabel.SetTop(14) f.statusLabel.SetWidth(140) f.statusLabel.SetHeight(32) f.statusLabel.SetCaption("●未连接") f.statusLabel.Font().SetSize(14) f.connectBtn = vcl.NewButton(f) f.connectBtn.SetParent(f) f.connectBtn.SetLeft(368) f.connectBtn.SetTop(8) f.connectBtn.SetWidth(80) f.connectBtn.SetHeight(topRowH) f.connectBtn.SetCaption("连接") f.connectBtn.Font().SetSize(14) f.connectBtn.SetOnClick(f.onConnect) f.refreshBtn = vcl.NewButton(f) f.refreshBtn.SetParent(f) f.refreshBtn.SetLeft(456) f.refreshBtn.SetTop(8) f.refreshBtn.SetWidth(80) f.refreshBtn.SetHeight(topRowH) f.refreshBtn.SetCaption("刷新") f.refreshBtn.Font().SetSize(14) f.refreshBtn.SetOnClick(f.onRefreshPorts) f.labU = vcl.NewLabel(f) f.labU.SetParent(f) f.labU.SetLeft(544) f.labU.SetTop(14) f.labU.SetCaption("单位:") f.labU.Font().SetSize(14) f.rbMM = vcl.NewRadioButton(f) f.rbMM.SetParent(f) f.rbMM.SetLeft(600) f.rbMM.SetTop(10) f.rbMM.SetCaption("mm") f.rbMM.SetChecked(f.cfg.DisplayUnit != "inch") f.rbMM.Font().SetSize(14) f.rbMM.SetOnClick(f.onUnitChange) f.rbInch = vcl.NewRadioButton(f) f.rbInch.SetParent(f) f.rbInch.SetLeft(670) f.rbInch.SetTop(10) f.rbInch.SetCaption("inch") f.rbInch.SetChecked(f.cfg.DisplayUnit == "inch") f.rbInch.Font().SetSize(14) f.rbInch.SetOnClick(f.onUnitChange) f.autoStartChk = vcl.NewCheckBox(f) f.autoStartChk.SetParent(f) f.autoStartChk.SetLeft(740) f.autoStartChk.SetTop(12) f.autoStartChk.SetCaption("开机启?) f.autoStartChk.Font().SetSize(14) f.autoStartChk.SetChecked(autostart.IsEnabled(injectedAppName)) f.autoStartChk.SetOnClick(f.onAutoStartToggle) } func (f *MainForm) initModuleB() { f.workOrderNoLabel = vcl.NewLabel(f) f.workOrderNoLabel.SetParent(f) f.workOrderNoLabel.SetLeft(16) f.workOrderNoLabel.SetTop(60) f.workOrderNoLabel.SetCaption("工单?") f.workOrderNoLabel.Font().SetSize(16) f.workOrderNoEdit = vcl.NewEdit(f) f.workOrderNoEdit.SetParent(f) f.workOrderNoEdit.SetLeft(80) f.workOrderNoEdit.SetTop(56) f.workOrderNoEdit.SetWidth(350) f.workOrderNoEdit.SetHeight(topRowH) f.workOrderNoEdit.SetTextHint("请输入工单号") f.workOrderNoEdit.Font().SetSize(16) f.queryBtn = vcl.NewButton(f) f.queryBtn.SetParent(f) f.queryBtn.SetLeft(438) f.queryBtn.SetTop(56) f.queryBtn.SetWidth(100) f.queryBtn.SetHeight(topRowH) f.queryBtn.SetCaption("查询") f.queryBtn.Font().SetSize(16) f.queryBtn.SetOnClick(f.onQueryWorkOrder) f.typeLabel = vcl.NewLabel(f) f.typeLabel.SetParent(f) f.typeLabel.SetLeft(540) f.typeLabel.SetTop(60) f.typeLabel.SetCaption("工件类型:") f.typeLabel.Font().SetSize(14) f.typeCombo = vcl.NewComboBox(f) f.typeCombo.SetParent(f) f.typeCombo.SetLeft(618) f.typeCombo.SetTop(56) f.typeCombo.SetWidth(120) f.typeCombo.SetHeight(topRowH) f.typeCombo.SetStyle(types.CsDropDownList) f.typeCombo.SetReadOnly(true) f.typeCombo.Font().SetSize(14) f.typeCombo.Items().Add("方形") f.typeCombo.Items().Add("圆形") f.countLabel = vcl.NewLabel(f) f.countLabel.SetParent(f) f.countLabel.SetLeft(746) f.countLabel.SetTop(60) f.countLabel.SetCaption("数量:") f.countLabel.Font().SetSize(14) f.countValue = vcl.NewLabel(f) f.countValue.SetParent(f) f.countValue.SetLeft(800) f.countValue.SetTop(60) f.countValue.SetWidth(60) f.countValue.SetCaption("--") f.countValue.Font().SetSize(14) f.statusLabel2 = vcl.NewLabel(f) f.statusLabel2.SetParent(f) f.statusLabel2.SetLeft(868) f.statusLabel2.SetTop(60) f.statusLabel2.SetCaption("状?") f.statusLabel2.Font().SetSize(14) f.statusValue = vcl.NewLabel(f) f.statusValue.SetParent(f) f.statusValue.SetLeft(918) f.statusValue.SetTop(60) f.statusValue.SetWidth(80) f.statusValue.SetCaption("--") f.statusValue.Font().SetSize(14) f.timeLabel = vcl.NewLabel(f) f.timeLabel.SetParent(f) f.timeLabel.SetLeft(1006) f.timeLabel.SetTop(60) f.timeLabel.SetCaption("创建时间:") f.timeLabel.Font().SetSize(14) f.timeValue = vcl.NewLabel(f) f.timeValue.SetParent(f) f.timeValue.SetLeft(1100) f.timeValue.SetTop(60) f.timeValue.SetWidth(170) f.timeValue.SetCaption("--") f.timeValue.Font().SetSize(14) } func (f *MainForm) initModuleC(workpieceType string) { for _, row := range f.rows { if row.panel != nil { row.panel.Free() } } f.rows = nil defs := squareItemDefs if workpieceType == "circle" { defs = circleItemDefs } f.collector = app.NewCollector(defs) f.rows = make([]itemRow, len(defs)) for i := 0; i < len(defs); i++ { row := itemRow{} row.panel = vcl.NewPanel(f) row.panel.SetParent(f) row.panel.SetBevelOuter(types.BvRaised) row.panel.SetColor(colors.ClBtnFace) row.name = vcl.NewLabel(row.panel) row.name.SetParent(row.panel) row.name.SetCaption(defs[i]) row.name.Font().SetSize(nameSize) row.name.SetAlignment(types.TaCenter) row.value = vcl.NewLabel(row.panel) row.value.SetParent(row.panel) row.value.SetCaption("----") row.value.Font().SetSize(valSize) row.value.SetAlignment(types.TaCenter) row.value.SetLayout(types.TlCenter) idx := i row.value.SetOnClick(func(s vcl.IObject) { initial, _ := f.collector.DisplayValue(idx, f.currentUnit()) if initial == "----" { initial = "" } val, ok := showNumKeyPad(f, initial) if ok { v, err := strconv.ParseFloat(val, 64) if err == nil { mm := v if f.rbInch.Checked() { mm = v * app.MmPerInch } f.collector.SetValue(idx, mm) f.refreshRows() } } }) row.unit = vcl.NewLabel(row.panel) row.unit.SetParent(row.panel) row.unit.SetCaption(f.currentUnitLabel()) row.unit.Font().SetSize(unitSize) row.unit.SetAlignment(types.TaCenter) row.panel.SetOnClick(func(s vcl.IObject) { f.onRowClick(idx) }) f.rows[i] = row } f.currentType = workpieceType } func (f *MainForm) initModuleD() { f.bottomLabel = vcl.NewLabel(f) f.bottomLabel.SetParent(f) f.bottomLabel.SetCaption("序列? - 末帧: -") f.bottomLabel.Font().SetSize(12) f.workpieceNoEdit = vcl.NewEdit(f) f.workpieceNoEdit.SetParent(f) f.workpieceNoEdit.SetWidth(180) f.workpieceNoEdit.SetHeight(44) f.workpieceNoEdit.SetTextHint("请输入工件号") f.workpieceNoEdit.Font().SetSize(14) f.workpieceNoEdit.SetOnExit(f.onWorkpieceNoExit) f.filterBtn = vcl.NewButton(f) f.filterBtn.SetParent(f) f.filterBtn.SetWidth(44) f.filterBtn.SetHeight(44) f.filterBtn.SetCaption("🔍") f.filterBtn.Font().SetSize(14) f.filterBtn.SetOnClick(f.onFilter) f.exportBtn = vcl.NewButton(f) f.exportBtn.SetParent(f) f.exportBtn.SetWidth(120) f.exportBtn.SetHeight(44) f.exportBtn.SetCaption("导出") f.exportBtn.Font().SetSize(14) f.exportBtn.SetOnClick(f.onExport) f.clearBtn = vcl.NewButton(f) f.clearBtn.SetParent(f) f.clearBtn.SetWidth(120) f.clearBtn.SetHeight(44) f.clearBtn.SetCaption("清除") f.clearBtn.Font().SetSize(14) f.clearBtn.SetOnClick(f.onClear) f.submitBtn = vcl.NewButton(f) f.submitBtn.SetParent(f) f.submitBtn.SetWidth(120) f.submitBtn.SetHeight(44) f.submitBtn.SetCaption("提交") f.submitBtn.Font().SetSize(14) f.submitBtn.SetOnClick(f.onSubmit) f.histView = vcl.NewListView(f) f.histView.SetParent(f) f.histView.SetViewStyle(types.VsReport) f.histView.SetReadOnly(true) f.histView.SetRowSelect(true) f.histView.SetGridLines(true) f.histView.Font().SetSize(12) f.refreshHistoryColumns() } func (f *MainForm) refreshHistoryColumns() { items := f.histView.Items() items.BeginUpdate() defer items.EndUpdate() for i := f.histView.Columns().Count() - 1; i >= 0; i-- { f.histView.Columns().Delete(i) } if f.currentType == "circle" { cols := []string{"时间", "工单?, "工件?, "分类", "球面直径 A", "球面轮廓?B", "球面位置 C"} for _, c := range cols { col := f.histView.Columns().Add() col.SetCaption(c) } } else { cols := []string{"时间", "工单?, "工件?, "分类", "弧面半径 B"} for _, c := range cols { col := f.histView.Columns().Add() col.SetCaption(c) } } } func (f *MainForm) currentUnit() app.Unit { if f.rbInch.Checked() { return app.UnitInch } return app.UnitMM } func (f *MainForm) currentUnitLabel() string { if f.rbInch.Checked() { return "inch" } return "mm" } func (f *MainForm) onUnitChange(sender vcl.IObject) { for _, row := range f.rows { row.unit.SetCaption(f.currentUnitLabel()) } f.refreshRows() } func (f *MainForm) onAutoStartToggle(sender vcl.IObject) { if f.autoStartChk.Checked() { if err := autostart.Enable(injectedAppName); err != nil { vcl.ShowMessage("设置开机启动失? " + err.Error()) f.autoStartChk.SetChecked(false) } } else { if err := autostart.Disable(injectedAppName); err != nil { vcl.ShowMessage("取消开机启动失? " + err.Error()) f.autoStartChk.SetChecked(true) } } } func (f *MainForm) onRowClick(i int) { f.collector.SetCurrent(i) f.refreshRows() } func (f *MainForm) onQueryWorkOrder(sender vcl.IObject) { workOrderNo := f.workOrderNoEdit.Text() if workOrderNo == "" { vcl.ShowMessage("请输入工单号") return } f.queryBtn.SetEnabled(false) go func() { ctx := context.Background() info, err := f.apiClient.QueryWorkOrder(ctx, workOrderNo) vcl.ThreadSync(func() { f.queryBtn.SetEnabled(true) if err != nil { vcl.ShowMessage("未查询到工单信息,请检查工单号是否正确") f.currentWorkOrder = nil f.countValue.SetCaption("--") f.statusValue.SetCaption("--") f.timeValue.SetCaption("--") return } f.currentWorkOrder = info f.updateWorkOrderInfo(info) }) }() } func (f *MainForm) updateWorkOrderInfo(info *api.WorkOrderInfo) { f.typeCombo.SetItemIndex(0) if info.WorkpieceType == "circle" || info.WorkpieceType == "GENERAL" { f.typeCombo.SetItemIndex(1) } f.countValue.SetCaption(strconv.Itoa(info.WorkpieceCount)) f.statusValue.SetCaption(info.StatusName) f.timeValue.SetCaption(info.CreatedAt) displayType := info.WorkpieceType if displayType == "GENERAL" { displayType = "circle" } if displayType != "" && displayType != f.currentType { f.initModuleC(displayType) f.refreshHistoryColumns() f.relayout() } } func (f *MainForm) onWorkpieceNoExit(sender vcl.IObject) { f.onFilter(nil) } func (f *MainForm) onFilter(sender vcl.IObject) { workOrderNo := f.workOrderNoEdit.Text() workpieceNo := f.workpieceNoEdit.Text() f.submitBtn.SetEnabled(false) go func() { ctx := context.Background() resp, err := f.apiClient.QueryMeasurements(ctx, workOrderNo, workpieceNo, 1, 100) vcl.ThreadSync(func() { f.submitBtn.SetEnabled(true) if err != nil { return } f.updateHistoryTable(resp.List) }) }() } func (f *MainForm) updateHistoryTable(records []api.MeasurementRecord) { items := f.histView.Items() items.BeginUpdate() defer items.EndUpdate() items.Clear() for _, r := range records { item := f.histView.Items().Add() item.SetCaption(formatTime(r.CreatedAt)) item.SubItems().Add(r.WorkOrderNo) item.SubItems().Add(r.WorkpieceNo) item.SubItems().Add(r.Category) if f.currentType == "circle" { item.SubItems().Add(fmt.Sprintf("%.3f", r.ValueA)) item.SubItems().Add(fmt.Sprintf("%.3f", r.ValueB)) item.SubItems().Add(fmt.Sprintf("%.3f", r.ValueC)) } else { item.SubItems().Add(fmt.Sprintf("%.3f", r.ValueA)) } } } func formatTime(s string) string { for _, layout := range []string{time.RFC3339, "2006-01-02 15:04:05"} { if t, err := time.Parse(layout, s); err == nil { return t.Format("01-02 15:04:05") } } return s } func (f *MainForm) onExport(sender vcl.IObject) { items := f.histView.Items() if items.Count() == 0 { vcl.ShowMessage("没有数据可导?) return } filename := time.Now().Format("20060102_150405") + ".csv" desktop, _ := os.UserHomeDir() filepath := desktop + "/Desktop/" + filename file, err := os.Create(filepath) if err != nil { vcl.ShowMessage("创建文件失败: " + err.Error()) return } defer file.Close() header := "时间,工单?工件?分类" if f.currentType == "circle" { header += ",球面直径 A,球面轮廓?B,球面位置 C" } else { header += ",弧面半径 B" } file.WriteString(header + "\n") for i := int32(0); i < items.Count(); i++ { item := items.Item(i) line := fmt.Sprintf("%s,%s,%s,%s", escapeCSV(item.Caption()), escapeCSV(item.SubItems().Strings(0)), escapeCSV(item.SubItems().Strings(1)), escapeCSV(item.SubItems().Strings(2))) if f.currentType == "circle" { line += fmt.Sprintf(",%s,%s,%s", item.SubItems().Strings(3), item.SubItems().Strings(4), item.SubItems().Strings(5)) } else { line += "," + item.SubItems().Strings(3) } file.WriteString(line + "\n") } vcl.ShowMessage("导出成功: " + filepath) } func escapeCSV(s string) string { if s == "" { return "" } if contains(s, ",") || contains(s, "\"") || contains(s, "\n") { return "\"" + replaceAll(s, "\"", "\"\"") + "\"" } return s } func contains(s, substr string) bool { if len(s) < len(substr) { return false } if s == substr { return true } for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false } func replaceAll(s, old, new string) string { result := "" for len(s) > 0 { if len(s) >= len(old) && s[:len(old)] == old { result += new s = s[len(old):] } else { result += s[:1] s = s[1:] } } return result } func (f *MainForm) onClear(sender vcl.IObject) { r := vcl.MessageDlg("确认清空当前工件数据?", types.MtConfirmation, types.MbYes, types.MbNo) if r == types.MrYes { f.collector.Clear() f.refreshRows() } } func (f *MainForm) onSubmit(sender vcl.IObject) { workOrderNo := f.workOrderNoEdit.Text() if workOrderNo == "" { vcl.ShowMessage("请先输入工单?) return } if f.currentWorkOrder == nil { vcl.ShowMessage("请先查询工单") return } workpieceNo := f.workpieceNoEdit.Text() if workpieceNo == "" { vcl.ShowMessage("请输入工件号") return } if !f.collector.AllFilled() { vcl.ShowMessage("请完成所有测?) return } workpieceType := f.currentWorkOrder.WorkpieceType if workpieceType == "GENERAL" { workpieceType = "circle" } req := api.SubmitRequest{ WorkOrderNo: workOrderNo, WorkpieceNo: workpieceNo, Category: "", WorkpieceType: workpieceType, ValueA: f.collector.Value(0), Unit: f.currentUnitLabel(), Serial: f.selectedPort(), Timestamp: time.Now().Format("2006-01-02 15:04:05"), } if workpieceType == "circle" { vb := f.collector.Value(1) vc := f.collector.Value(2) req.ValueB = &vb req.ValueC = &vc } f.submitBtn.SetEnabled(false) go func() { ctx := context.Background() _, err := f.apiClient.SubmitMeasurement(ctx, req) vcl.ThreadSync(func() { f.submitBtn.SetEnabled(true) if err != nil { vcl.ShowMessage("提交失败: " + err.Error()) return } vcl.ShowMessage("提交成功") f.collector.Clear() f.refreshRows() f.onFilter(nil) }) }() } func (f *MainForm) onConnect(sender vcl.IObject) { if f.serial != nil { _ = f.serial.Close() f.serial = nil f.statusLabel.SetCaption("●未连接") f.statusLabel.Font().SetColor(colors.ClBtnFace) f.connectBtn.SetCaption("连接") f.reconnT.SetEnabled(false) return } f.tryConnectSerial() } func (f *MainForm) selectedPort() string { idx := f.portCombo.ItemIndex() if idx < 0 { return "" } var s string f.portCombo.GetTextBuf(&s, 64) return s } func (f *MainForm) refreshPortList() { ports, err := serial.GetPortsList() if err != nil { log.Printf("列出串口失败: %v", err) return } f.portCombo.Clear() sel := int32(-1) for i, p := range ports { f.portCombo.Items().Add(p) if p == f.cfg.SerialPort { sel = int32(i) } } if sel >= 0 { f.portCombo.SetItemIndex(sel) } else if len(ports) > 0 { f.portCombo.SetItemIndex(0) } } func (f *MainForm) onRefreshPorts(sender vcl.IObject) { f.refreshPortList() } func (f *MainForm) tryConnectSerial() { if f.serial != nil { return } name := f.selectedPort() if name == "" { f.statusLabel.SetCaption("●未选择串口") f.statusLabel.Font().SetColor(colors.ClRed) return } sp, err := serialport.Open(name, f.cfg.BaudRate) if err != nil { f.statusLabel.SetCaption(fmt.Sprintf("%s ●未连接", name)) f.statusLabel.Font().SetColor(colors.ClRed) f.reconnT.SetEnabled(true) return } f.serial = sp f.statusLabel.SetCaption(fmt.Sprintf("%s ●已连接", name)) f.statusLabel.Font().SetColor(colors.ClGreen) f.connectBtn.SetCaption("断开") f.reconnT.SetEnabled(false) go f.consumeFrames() } func (f *MainForm) consumeFrames() { for { select { case fr, ok := <-f.serial.Frames(): if !ok { return } vcl.ThreadSync(func() { f.collector.ApplyFrame(fr) f.refreshRows() }) case err, ok := <-f.serial.Errors(): if !ok { return } log.Printf("串口错误: %v", err) vcl.ThreadSync(func() { f.statusLabel.SetCaption("●已断开") f.statusLabel.Font().SetColor(colors.ClRed) f.serial = nil f.connectBtn.SetCaption("连接") f.reconnT.SetEnabled(true) }) return } } } func (f *MainForm) refreshRows() { u := f.currentUnit() cur := f.collector.CurrentIndex() for i, row := range f.rows { s, _ := f.collector.DisplayValue(i, u) row.value.SetCaption(s) row.unit.SetCaption(f.currentUnitLabel()) if i == cur { row.panel.SetColor(types.TColor(0x00FFE0C0)) } else { row.panel.SetColor(colors.ClBtnFace) } } sn, seq := f.collector.Serial() if sn == 0 { f.bottomLabel.SetCaption("序列? - 末帧: -") } else { f.bottomLabel.SetCaption(fmt.Sprintf("序列? %d 末帧 seq=%d %s", sn, seq, time.Now().Format("15:04:05"))) } } func (f *MainForm) onResize(sender vcl.IObject) { f.relayout() } func (f *MainForm) relayout() { cw := f.ClientWidth() ch := f.ClientHeight() if cw <= 0 || ch <= 0 { return } sx := float64(cw) / float64(baseW) sy := float64(ch) / float64(baseH) val := clamp(int32(float64(valSize)*sy), minFont, maxFont) name := clamp(int32(float64(nameSize)*sy), minFont/2, maxFont/2) unit := clamp(int32(float64(unitSize)*sy), minFont/2, maxFont/2) gap := int32(float64(gapX) * sx) if gap < 4 { gap = 4 } ptop := int32(108) pH := int32(float64(panelH) * sy) if pH < 100 { pH = 100 } var pW int32 if len(f.rows) == 1 { pW = cw - 32 } else { pW = (cw - gap*(int32(len(f.rows))+1) - 32) / int32(len(f.rows)) } if pW < 100 { pW = 100 } for i, row := range f.rows { left := int32(16) + int32(i)*(pW+gap) row.panel.SetLeft(left) row.panel.SetTop(ptop) row.panel.SetWidth(pW) row.panel.SetHeight(pH) nameH := name + 12 unitH := unit + 12 vH := val + 8 mid := pH / 2 valTop := mid - vH/2 if valTop < 8+nameH { valTop = 8 + nameH } if valTop+vH > pH-8-unitH { valTop = pH - 8 - unitH - vH } row.name.SetLeft(8) row.name.SetTop(8) row.name.SetWidth(pW - 16) row.name.SetHeight(nameH) row.name.Font().SetSize(name) row.value.SetLeft(8) row.value.SetTop(valTop) row.value.SetWidth(pW - 16) row.value.SetHeight(vH) row.value.Font().SetSize(val) row.unit.SetLeft(8) row.unit.SetTop(pH - 8 - unitH) row.unit.SetWidth(pW - 16) row.unit.SetHeight(unitH) row.unit.Font().SetSize(unit) } btnH := int32(float64(44) * sy) if btnH < 36 { btnH = 36 } btnW := int32(float64(120) * sx) if btnW < 80 { btnW = 80 } btnFont := clamp(int32(float64(14)*sy), 12, 28) bottomY := ptop + pH + 12 f.bottomLabel.SetLeft(16) f.bottomLabel.SetTop(bottomY) f.bottomLabel.SetWidth(cw - 32) btnTop := ptop + pH + 36 f.workpieceNoEdit.SetLeft(16) f.workpieceNoEdit.SetTop(btnTop) f.workpieceNoEdit.SetWidth(int32(float64(180) * sx)) f.workpieceNoEdit.SetHeight(btnH) f.filterBtn.SetLeft(16 + int32(float64(180)*sx) + 4) f.filterBtn.SetTop(btnTop) f.filterBtn.SetWidth(btnH) f.filterBtn.SetHeight(btnH) f.exportBtn.SetLeft(cw - 3*(btnW+16) - 16) f.exportBtn.SetTop(btnTop) f.exportBtn.SetWidth(btnW) f.exportBtn.SetHeight(btnH) f.exportBtn.Font().SetSize(btnFont) f.clearBtn.SetLeft(cw - 2*(btnW+16) - 16) f.clearBtn.SetTop(btnTop) f.clearBtn.SetWidth(btnW) f.clearBtn.SetHeight(btnH) f.clearBtn.Font().SetSize(btnFont) f.submitBtn.SetLeft(cw - btnW - 16) f.submitBtn.SetTop(btnTop) f.submitBtn.SetWidth(btnW) f.submitBtn.SetHeight(btnH) f.submitBtn.Font().SetSize(btnFont) histTop := btnTop + btnH + 8 histH := ch - histTop - 8 if histH < 60 { histH = 60 } f.histView.SetLeft(16) f.histView.SetTop(histTop) f.histView.SetWidth(cw - 32) f.histView.SetHeight(histH) histFont := clamp(int32(float64(12)*sy), 10, 20) f.histView.Font().SetSize(histFont) var colWidths []int32 if f.currentType == "circle" { colWidths = []int32{ int32(float64(130) * sx), int32(float64(130) * sx), int32(float64(110) * sx), int32(float64(90) * sx), int32(float64(120) * sx), int32(float64(120) * sx), } } else { colWidths = []int32{ int32(float64(130) * sx), int32(float64(130) * sx), int32(float64(110) * sx), int32(float64(90) * sx), } } used := int32(0) for _, w := range colWidths { used += w } lastW := cw - 32 - used if lastW < 100 { lastW = 100 } for i, w := range append(colWidths, lastW) { if int32(i) < f.histView.Columns().Count() { f.histView.Column(int32(i)).SetWidth(w) } } } func clamp(v, lo, hi int32) int32 { if v < lo { return lo } if v > hi { return hi } return v } func (f *MainForm) OnFormClose(sender vcl.IObject, action *types.TCloseAction) { r := vcl.MessageDlg("确定要退出程序吗?", types.MtConfirmation, types.MbYes, types.MbNo) if r != types.MrYes { *action = types.CaNone return } if f.serial != nil { _ = f.serial.Close() } }