自定义图形控件

我们实现一个myswitch功能,用于显示电路开关状态。

操作步骤如下:

  1. 准备2张SVG图片, open.svg close.svg

  2. 在/usr/src/app/FUXA/client/src/app/gauges/controls/路径下,添加我们的html-myswitch目录和文件。 /usr/src/app/FUXA/client/src/app/gauges/controls/html-myswitch/html-myswitch.component.ts

import { Injectable, ViewContainerRef, ComponentFactoryResolver } from '@angular/core';
import { GaugeBaseComponent } from '../../gauge-base/gauge-base.component';
import { GaugeSettings, Variable, GaugeStatus, WindowLink, Event } from '../../../_models/hmi';
import { Utils } from '../../../_helpers/utils';
import { GaugeDialogType } from '../../gauge-property/gauge-property.component';
import { NgxMySwitchComponent } from '../../../gui-helpers/ngx-myswitch/ngx-myswitch.component';



@Injectable()
export class HtmlMySwitchComponent {
    static TypeTag = 'svg-ext-own_ctrl-myswitch';
    static LabelTag = 'HtmlMySwitch';
    static prefix = 'D-OXC_';

    constructor() {
    }

    static getSignals(pro: any) {
        let res: string[] = [];
        if (pro.variableId) {
            res.push(pro.variableId);
        }
        if (pro.alarmId) {
            res.push(pro.alarmId);
        }
        if (pro.actions) {
            pro.actions.forEach(act => {
                res.push(act.variableId);
            });
        }
        return res;
    }

    static getDialogType(): GaugeDialogType {
        return GaugeDialogType.Switch;
    }

    static bindEvents(ga: GaugeSettings, slider?: NgxMySwitchComponent, callback?:any): Event {
        if (slider) {
            slider.bindUpdate((val) => {
                let event = new Event();
                event.type = 'on';
                event.ga = ga;
                event.value = val;
                callback(event);
            });
        }
        return null;
    }

    static processValue(ga: GaugeSettings, svgele: any, sig: Variable, gaugeStatus: GaugeStatus, switcher?: NgxMySwitchComponent) {
        try {
            if (switcher) {
                let val = parseFloat(sig.value);
                if (Number.isNaN(val)) {
                    // maybe boolean
                    val = Number(sig.value);
                } else {
                    val = parseFloat(val.toFixed(5));
                }
                switcher.setValue(val);
            }
        } catch (err) {
            console.error(err);
        }
    }

    static initElement(ga: GaugeSettings, resolver: ComponentFactoryResolver, viewContainerRef: ViewContainerRef, options?: any) {
        let ele = document.getElementById(ga.id);
        if (ele) {
            let htmlSwitch = Utils.searchTreeStartWith(ele, this.prefix);
            if (htmlSwitch) {
                const factory = resolver.resolveComponentFactory(NgxMySwitchComponent);
                const componentRef = viewContainerRef.createComponent(factory);
                htmlSwitch.innerHTML = '';

                componentRef.changeDetectorRef.detectChanges();
                const loaderComponentElement = componentRef.location.nativeElement;
                htmlSwitch.appendChild(loaderComponentElement);
                // var options = {}
                // options.height = htmlSwitch.clientHeight;
                // options.width = htmlSwitch.clientWidth;
                // if (!componentRef.instance.setOptions(options, true)) {
                //     // componentRef.instance.init();
                // }
                return componentRef.instance;
            }
        }
    }

    static resize(ga: GaugeSettings, resolver: ComponentFactoryResolver, viewContainerRef: ViewContainerRef, options?: any) {
        let ele = document.getElementById(ga.id);
        if (ele) {
            let htmlSwitch = Utils.searchTreeStartWith(ele, this.prefix);
            if (htmlSwitch) {
                const factory = resolver.resolveComponentFactory(NgxMySwitchComponent);
                const componentRef = viewContainerRef.createComponent(factory);
                htmlSwitch.innerHTML = '';

                componentRef.changeDetectorRef.detectChanges();
                const loaderComponentElement = componentRef.location.nativeElement;
                htmlSwitch.appendChild(loaderComponentElement);
                return componentRef.instance;
            }
        }
    }

    static detectChange(ga: GaugeSettings, res: any, ref: any) {
        let options;
        if (ga.property && ga.property.options) {
            options = ga.property.options;
        }
        return HtmlMySwitchComponent.initElement(ga, res, ref, options);
    }

    static getSize(ga: GaugeSettings) {
        let result = {height: 0, width: 0};
        let ele = document.getElementById(ga.id);
        if (ele) {
            let htmlSwitch = Utils.searchTreeStartWith(ele, this.prefix);
            if (htmlSwitch) {
                result.height = htmlSwitch.clientHeight;
                result.width = htmlSwitch.clientWidth;
            }
        }
        return result;
    }
}

注意:

  • 类开头的3个变量: static TypeTag = ‘svg-ext-own_ctrl-myswitch’; static LabelTag = ‘HtmlMySwitch’; static prefix = ‘D-OXC_’; TypeTag必须保留svg-ext-own_ctrl-前缀,最后的myswitch可以自己命令 LabelTag正常用。 prefix,这个D-OXC_不能改,必须保持原样。因为svgeditor并没有开源,其内部对自定义控件的前缀做了限制。如果你想改,需要对已经作为混淆的js进行调整。

  • 这个里用到一个NgxMySwitchComponent文件,这个是我们真实的图形。

/usr/src/app/FUXA/client/src/app/gui-helpers/ngx-myswitch

/usr/src/app/FUXA/client/src/app/gui-helpers/ngx-myswitch/ngx-myswitch.component.html

<label class="md-switch" style="width:100%;height:100%;">
    <input type="checkbox" (change)="onClick()" #switcher>

        <svg #openW width="100%"  version="1.1" viewBox="0 0 8.2021 2.6458" xmlns="http://www.w3.org/2000/svg">
            <g transform="translate(-1.1811 -.40369)">
             <g transform="translate(-29.104,-80.172)">
              <g stroke="#000" stroke-linecap="round" stroke-linejoin="round">
               <path d="m30.427 82.687h2.6458" fill="none" stroke-width=".265"/>
               <path d="m35.719 82.687h2.6458" fill="none" stroke-width=".265"/>
               <ellipse cx="33.073" cy="82.687" rx=".40117" ry=".39902" stroke-width=".256" style="paint-order:normal"/>
               <ellipse cx="35.719" cy="82.687" rx=".40117" ry=".39902" fill="#fff" stroke-width=".256" style="paint-order:normal"/>
               <path d="m33.073 82.687 2.1167-1.8521" fill="none" stroke-width=".265"/>
              </g>
             </g>
            </g>
           </svg>

        <svg  #closeW width="100%" version="1.1" viewBox="0 0 8.2021 2.6458" xmlns="http://www.w3.org/2000/svg">
            <g transform="translate(-1.8476 -5.1592)">
             <g transform="translate(-28.447 -79.398)">
              <g stroke="#000" stroke-linecap="round" stroke-linejoin="round">
               <path d="m30.427 86.656h2.6458" fill="none" stroke-width=".265"/>
               <path d="m35.719 86.656h2.6458" fill="none" stroke-width=".265"/>
               <ellipse cx="33.073" cy="86.656" rx=".40117" ry=".39902" stroke-width=".256" style="paint-order:normal"/>
               <ellipse cx="35.719" cy="86.656" rx=".40117" ry=".39902" fill="#fff" stroke-width=".256" style="paint-order:normal"/>
               <path d="m33.073 86.656 2.6458-0.52917" fill="none" stroke-width=".265"/>
              </g>
             </g>
            </g>
           </svg>
</label>

这里面的2个svg就是我们的open和close,这个文件看起来是标准的angular component html文件,但是ngIf会报错,估计是和创建方式有关系。

然后需要注意的是style=“width:100%;height:100%",这个控制图形会随着外部操作拖拽变大或者变小。

/usr/src/app/FUXA/client/src/app/gui-helpers/ngx-myswitch/ngx-myswitch.component.ts

/* eslint-disable @angular-eslint/component-selector */
import { Component, OnInit, Input, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
    selector: 'ngx-myswitch',
    templateUrl: './ngx-myswitch.component.html',
    styleUrls: ['./ngx-myswitch.component.css']
})
export class NgxMySwitchComponent implements AfterViewInit {

    @ViewChild('switcher', {static: false}) public switcher: ElementRef;
    @ViewChild('openW', {static: false}) public openW: ElementRef;
    @ViewChild('closeW', {static: false}) public closeW: ElementRef;
    options: SwitchOptions = new SwitchOptions();
    checked = false;
    onUpdate: any;
    isOpen = false;
    constructor() { 
    }

    ngAfterViewInit() {
        this.onRefresh();
    }

    onClick() {
        this.onRefresh();
        if (this.onUpdate) {
            this.onUpdate((this.checked) ? this.options.onValue.toString() : this.options.offValue.toString());
        }
    }

    onRefresh() {
        this.checked = this.switcher.nativeElement.checked;
        if(this.checked)
        {
            this.openW.nativeElement.style.display = "none";
            this.closeW.nativeElement.style.display = "block";
        }else{
            this.openW.nativeElement.style.display = "block";
            this.closeW.nativeElement.style.display = "none";
        }        
    }

    setOptions(options: SwitchOptions, force = false): boolean {
        if (force) {
            this.options = options;
            this.onRefresh();
        } else {
            setTimeout(() => {
                this.options = options;
                this.onRefresh();
            }, 200);
        }
        return true;
    }

    setValue(value: number) {
        this.switcher.nativeElement.checked = (value) ? true : false;
        this.onRefresh();
    }

    bindUpdate(calback: any) {
        this.onUpdate = calback;
    }
}

export class SwitchOptions {
    offValue = 0;
    onValue = 1;
    offBackground = '#ccc';
    onBackground = '#ccc';
    offText = '';
    onText = '';
    offSliderColor = '#fff';
    onSliderColor = '#0CC868';
    offTextColor = '#000'
    onTextColor = '#fff'
    fontSize = 12
    fontFamily = ''
    radius = 0;
    height: number;
    width: number;
}

这个里面因此了一个checkbox处理用户的点击,onRefresh根据当前是选中还是未选中,控制2个图型的显示。

  1. app.module需要注册我们的组件 /usr/src/app/FUXA/client/src/app/app.module.ts

L123:

//JGCX 2023-08-08
import { HtmlMySwitchComponent } from './gauges/controls/html-myswitch/html-myswitch.component';

L335:

//JGCX
HtmlMySwitchComponent,
  1. gauges.component.ts 需要注册我们的组件 /usr/src/app/FUXA/client/src/app/gauges/gauges.component.ts

L40:

//JGCX 2023-08-08  Add Our own gauge
import { HtmlMySwitchComponent } from './controls/html-myswitch/html-myswitch.component';

L72:

    // list of gauges components
    static Gauges = [ValueComponent, HtmlInputComponent, HtmlButtonComponent, HtmlBagComponent,
        HtmlSelectComponent, HtmlChartComponent, GaugeProgressComponent, GaugeSemaphoreComponent, ShapesComponent, ProcEngComponent, ApeShapesComponent,
        PipeComponent, SliderComponent, HtmlSwitchComponent, HtmlGraphComponent, HtmlIframeComponent, HtmlTableComponent, HtmlMySwitchComponent];

L206:

 else if (ga.type.startsWith(HtmlMySwitchComponent.TypeTag)) {
            return this.mapGauges[ga.id] = HtmlMySwitchComponent.detectChange(ga, res, ref);
        } 

L477:

else if (ga.type.startsWith(HtmlMySwitchComponent.TypeTag)) {
            let self = this;
            HtmlMySwitchComponent.bindEvents(ga, this.mapGauges[ga.id], (event) => {
                self.putEvent(event);
            });
        }

L546:

else if (ga.type.startsWith(HtmlMySwitchComponent.TypeTag)) {
                    Object.keys(this.memorySigGauges[sig.id]).forEach(k => {
                        if (k === ga.id && this.mapGauges[k]) {
                            HtmlMySwitchComponent.processValue(ga, svgele, sig, gaugeStatus, this.mapGauges[k]);
                        }
                    });
                    break;
                }       

L715:

else if (type.startsWith(HtmlMySwitchComponent.TypeTag)) {
            return 'myswitch_';
        }

L811:

else if (ga.type.startsWith(HtmlMySwitchComponent.TypeTag)) {
            let gauge = HtmlMySwitchComponent.initElement(ga, res, ref, isview);
            this.mapGauges[ga.id] = gauge;
            return gauge;
        }
  1. 工具栏,我们需要找个地方添加 /usr/src/app/FUXA/client/src/app/editor/editor.component.html

L185

<!-- JGCX -->
<div class="svg-tool-button" matTooltip="{{'editor.controls-myswitch' | translate}}" [ngClass]="{'svg-tool-active': isModeActive('own_ctrl-myswitch')}"
    (click)="setMode('own_ctrl-myswitch')">
    <span class="icon-tool icon-myswitch"></span>
</div>

/usr/src/app/FUXA/client/src/app/editor/editor.component.css

.icon-myswitch {
    background: url("assets/images/open.svg") no-repeat center center;
}

然后调试好,就可以用了。