客制化控件 + 实现客户端设备集成 (仅支持到WPF的GUI客户端)

栏目:云星空知识作者:金蝶来源:金蝶云社区发布:2024-09-23浏览:8

客制化控件 + 实现客户端设备集成 (仅支持到WPF的GUI客户端)

本篇基础:BOS平台的二开编程(论坛),C#编程(ms),WPF编程(ms),Python语法, XML语法,VistualStudio2012(up)工具(ms),C/S基础知识,x86与x64基础知识


【从老论坛中搬家到帖子里,原贴地址【客制化控件插件实现客户端设备集成(桌面客户端功能

论坛不支持评论编辑和附件,也不支持回车换行。

如果实在看不清楚可以参考老论坛帖子:https://vip.kingdee.com/article/39283 


( 建议所有使用这篇文章指导进行客制化控件二开项目的同学,回帖简单说明你的项目简介,这样也可以协助推广你的项目,其他需要类似项目的同学也可以通过帖子联系你进行商务协助 -- 这个仅是一个建议 )


一、背景

    1、应用背景:在企业应用中广泛存在一些接入设备,如电子行业的激光扫描设备,IC卡刷卡设备,医疗行业的电子秤,射频读卡器,影像设备等。接口类型没有同一标准,有的采用RS232,RS485,USB等,有的采用FTP文件服务器,有的采用Smart Card接口组件,驱动设备也各行其是。K/3 Cloud作为一个通用平台,不能包罗所有的第三方接入驱动。因此,我们开放一个桌面端的接口模型,实现这些设备的二次开发接入。

    2、开发技术要求:基于DotNet FrameWork 4.0的WPF编程技术,基于C#编程语言,有C/S编程基础概念,有VistualStudio工具使用经验。


二、部署二次开发组件

1、在K/3 Cloud服务器安装目录 的【WebSite\ClientBin\K3CloudClient\controlplugins】目录下部署二次开发.deploy组件。具体部署方法参考《答疑3:通过BOSIDE新建解决方案或应用方案


2、BOSIDE中拖放自定义控件

2.1、在自定义控件的属性列表中找到【引用组件】属性,把相关参考组件注册到该属性里面。

--2.2、相关依赖组件注册在BOSIDE中的菜单【文件--引用组件管理】功能进行关联依赖组件注册。(如果是用1的安装包方式部署,可以不用设置引用组件)。

--2.3、如果更新组件,则修改参考组件列表中对应组件的版本信息即可。(如果是用1的安装包方式部署,可以不用设置引用组件)。

--2.4、如果使用了这个BOSIDE注册相关组件的方法,那么就不需要做下面“3、配置MainDownloadList.xml注册文件(备选方案)”这个步骤了,该步骤可以忽略


--3、配置MainDownloadList.xml注册文件备选方案,有需要请到答疑部分查看,用1的安装包方式部署,可以不需要关注这种方式


4、启动K/3 Cloud客户端桌面版程序(双击桌面的快捷图标),这时程序会自动按照服务器地址设定检查组件版本,并按需要下载相关客制化组件到本地的 【%Program Files %\Kingdee\K3Cloud\DeskClient\K3CloudClient\controlplugins】目录下。按多数客户的需求将把客制化组件目录调整到Kingdee.BOS.Xpf.App.exe执行文件所在目录的子目录controlplugins下,一般会在【%Program Files %\Kingdee\K3Cloud\DeskClient\K3CloudClient\controlplugins】这个位置里面,方便二开程序定位自己的数据文件。)


5、客制化控件关联引用需要部署controlplugins,或者需要引用K3CloudClient目录组件时,可以配置Kingdee.BOS.XPF.App.exe.config文件增加如下配置(6.0或5.0 2015-07-09补丁已经默认配置,如没有成功配置,可以手工添加):

<?xml version="1.0"?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="controlplugins"/>
    </assemblyBinding>
  </runtime>
</configuration>


三、客制化组件开发

1、在Visual Studio 2012中新增类库工程;如果需要新增有WPF界面元素的,必须新建WPF类库类型的工程。(注意,如果使用的控件是32位平台的,那么客制化组件工程文件指定目标平台为x86)

2、引用服务器安装目录下【WebSite/ClientBin/CustomControl/WPF/Kingdee.BOS.XPF.ControlPlugins.Contracts.dll】基础组件;

3、新建类,编写客制化控件桌面客户端代码如下:

    /// <summary>

    /// 有界面插件,如果不需要界面展示的,无须继承任何FrameworkElement的子类

    /// </summary>

    public class SmartCardReader : ContentControl, IKDCustomControl
        , IDynamicFormSupported
    {


        /// <summary>

        /// 下载客户端后,在 Program Files (x86)\Kingdee\K3Cloud\DeskClient\K3CloudClient\目录下有Kingdee.BOS.Client.Core.dll 等相关组件可供引用

        /// 实现单据代理接口,以便通过this.FormProxy.ControlFactoryProxy.GetCmp(Key)即可获取到其他单据上其他字段的Proxy,从而控制其他字段的内容。

        /// 接口IDynamicFormSupported 来自Kingdee.BOS.Client.Core.dll(2015-06-18以及之后的补丁提供该接口);

        /// 如果不需要操控表单其他控件元素,就不需要实现这个接口。可以通过this.FormProxy.ControlFactoryProxy.GetCmp(控件标识).GetControl()获取控件实例,实现定制交互逻辑);

        /// </summary>

        public IKDDynamicFormProxy FormProxy { get; set; }

        /// <summary>

        /// 设置单据头字段数据

        /// <summary>

        private void SetFNumberValue()

        {

                var itemCt = this.ControlFactoryProxy.GetCmp("FNUMBER") as IKDItemContainerProxy;

                var innEditor = itemCt.ControlProxy as IKDControlProxy;

                var edit = ((innEditor as IKDControlProxy).GetControl() as DevExpress.Xpf.Editors.IBaseEdit);

                edit.EditValue = "AABBCC";

        }


        public IKDCustomControlProxy Proxy { get; set; }

        TextBlock _txtDsp = null;

        protected void FireOnCustomEvent(CustomEventArgs e)

        {

            if (this.Proxy != null)

            {

                this.Proxy.FireCustomEvent(e);

            }

        }


        /// <summary>

        /// 界面组件初始化,如果没有界面要求,这里保留为空即可。

        /// </summary>

        public void InitComponent()

        {

            var label = "This is Custom control in Kingdee.XPF.CustomControlPlugins, named SmartCardReader";

            this._txtDsp = new TextBlock()

            {

                ToolTip = label,

                TextWrapping = System.Windows.TextWrapping.Wrap,

                Text = label

            };

            this.Content = new Border()

            {

                BorderThickness = new Thickness(1),

                BorderBrush = new SolidColorBrush(Colors.Blue),

                Child = this._txtDsp

            };

        }


        /// <summary>

        /// 用于资源释放,在单据关闭时调用

        /// </summary>


        public void Release()

        {

            this.Content = null;

        }



///************以下接口按自己需要添加*************////


        /// <summary>

        /// 定制控件入口,服务端调用方法:this.View.GetControl("FCUSTOMCONTROL").InvokeControlMethod("DoCustomMethod","WriteString",args),args是对象数组

        /// </summary>

        /// <param name="data"></param>

        public void WriteString(string data)

        {

            this.Dispatcher.BeginInvoke(new Action(() =>

            {

                if (this._txtDsp != null)

                {

                    this._txtDsp.Text = data;

                    this.Proxy.FireCustomEvent(new CustomEventArgs("", "Success", "{message:'WriteString() Success!'}"));

                }

                else

                {

                    this.Proxy.FireCustomEvent(new CustomEventArgs("", "Error", "{message:'WriteString() Error, component uninitilization!'}"));

                }

            }));

        }


    }




4、编写客制化控件服务端表单插件代码如下:

4.1、在BOS IDE中拖放自定义控件:(推荐方案)

4.1.1、直接在BOSIDE中拖放一个【通用控件--自定义控件】,然后填写属性【程序集】,【类名】,【命名空间】就可以了。(这个方法将替代4.2步骤,在2015-05-07号补丁后, 下面4.2步骤就可以省略了。)

d995eefa80ab4770bc736578b5f95f8e.webp


4.1.2、如果存在相关参考依赖组件,可以在BOSIDE的菜单【文件】-【引用组件管理】中设置

(* 注意:如果是用安装包方式部署相关依赖组件,可以不用设置引用组件,同时【禁止自定义控件下载】属性应该勾选)。

如下图:(设置比较简单,界面上也有说明!)

94dedcf91cd54d32b8eb86c9b25f2d2d.webp


4.2、构造插件方式实现客制化控件:(备选方案, 参考答疑5


4.3、表单业务插件:如下代码(以Python为例,可以自行翻译为C#代码):

C#代码例子:

using Kingdee.BOS.Core.DynamicForm.PlugIn.ControlModel;
using Kingdee.BOS.Core.DynamicForm.PlugIn.Args;

namespace Kingdee.Texst.CustomControl
{
    [Description("测试客制化控件业务插件")]
    public class CustomControl : AbstractBillPlugIn
    {//实现定制控件的自定义接口调用
        public override void CustomEvents(CustomEventsArgs e)
        {
            base.CustomEvents(e);
            if (e.Key == " FCUSTOMCONTROL ")
            {
                var recData = e.EventArgs;
            }
        }

        public override void ButtonClick(ButtonClickEventArgs e)
        {
            if (e.Key == "FWRITESTRING")
            {
                var str1 = this.Model.GetValue("FTEXT");
                var args = new object[1];
                args[0] = str1;
                //#实现定制控件的自定义接口调用
                this.View.GetControl("FCUSTOMCONTROL").InvokeControlMethod("DoCustomMethod", "WriteString", args);
                // 特别注意:这里的args参数应对是一个对象数组,在C#中如下编码;
                //0个参数的情况: var args = new object[1]; args[0] = null;
                //1个参数的情况: var args = new object[1]; args[0] = str1;
                //2个参数的情况: var args = new object[2]; args[0] = str1; args[1] = str2;
                //n个参数的情况: var args = new object[n]; args[0] = str1; args[1] = str2; ... args[n-1] = strn;
            }
        }
    }
}

python代码例子:

from System import *
from Kingdee.BOS.Core.DynamicForm.PlugIn.ControlModel import *

def ButtonClick(e):
    if(e.Key=="FWRITESTRING"):
        str1 = this.Model.GetValue("FTEXT")
        args =[ str1]
        #实现定制控件的自定义接口调用
        this.View.GetControl("FCUSTOMCONTROL").InvokeControlMethod( "DoCustomMethod", "WriteString", args)
        #特别注意:这里的args参数应对是一个对象数组,在C#中如下编码;
        #0个参数的情况: var args = new object[1]; args[0] = null;
        #1个参数的情况: var args = new object[1]; args[0] = str1;
        #2个参数的情况: var args = new object[2]; args[0] = str1; args[1] = str2;
        #n个参数的情况: var args = new object[n]; args[0] = str1; args[1] = str2; ... args[n-1] = strn;

#这里获取服务端发送的数据
def CustomEvents(e):
    if(e.Key==" FCUSTOMCONTROL "):
        recData = e. eventArgs



四、屏蔽其他端使用该表单

    因为本功能仅允许在桌面使用,其他端必须进行功能屏蔽。编写业务插件如下:

        public override void PreOpenForm(Core.DynamicForm.PlugIn.Args.PreOpenFormEventArgs e)
        {
            if (e.Context.ClientType != ClientType.WPF)
            {
                e.CancelMessage = "不支持桌面应用的功能,请在桌面应用上使用该功能。";
                e.Cancel = true;
            }
            base.PreOpenForm(e);
        }

[或者]显示界面,但不加载第三方ActiveX控件,选择动态加载ActiveX组件的方法。
就是初始化时不加载,控件实现InitThirdPart()方法来初始加载ActiveX控件(类似例子中的WriteString方法),由服务端调用InitThirdPart()才加载控件。
服务端业务插件中AfterBindingData接口重载后判断this.Context.ClientType==ClientType.WPF就调用InitThirdPart()方法,否则就不调用。客制化控件就是给你自己灵活使用的,自己变通想下就可以解决.



五、附件:测试例子用的动态表单元数据

【看跟贴一楼】

容易出错的情况参考帖子:

https://vip.kingdee.com/article/31771

第三方组件编译目标平台参考帖子:

https://vip.kingdee.com/article/33883

https://vip.kingdee.com/article/36102



六、答疑篇


答疑1】:

怎么在客制化控件里控制别的字段, 比如在BOS加了个文本字段FText,怎么在客制化控件桌面客户端代码里为其赋值?


【答】:参考代码:

 IKDControlProxy ctl = this.ControlFactoryProxy.GetCmp(key.ToUpper());

 if (ctl != null)

 {

   JObject fldState = new JObject();

   fldState["key"] = "FXXX";

   fldState["value"] = "xxxxx";

   ctl.UpdateState(fldState);

 }


答疑2】:

写客制化控件插件,出现报错“试图加载格式不正确的程序。”请问这个是什么原因造成和有什么解决方法?

【答】:

注意目标引用的第三方组件是x86的还是x64的。

如果运行在window x86平台的要部署第三方的x86的组件;

如果运行在window x64平台的要部署对于第三方的x64的组件;

如果第三方组件的x86版可以运行在window64平台上,那么你的dll就必须编译成x86的,并且部署x86版本的第三方组件。


如果没有第三方组件引用,就用anycpu编译!
0,检查是否5.0 最新补丁,或者6.0 最新补丁,5.0一定要打最新补丁;
1,检查本地组件路径是否正确;
2,检查配置的命名空间和类名是否正确;
3,检查目标平台是否正确 ,关于微软的x86与x64匹配情况:

必须符合这三种组合之一,不符合的跑不了,这事微软定的规矩:
--3.1、x86win   x86/anycpu的k3CloudApp   x86第三方组件   x86的第三方依赖组件...;
--3.2、x64win   x64/anycpu的k3CloudApp   x64第三方组件   x64的第三方依赖组件...;
--3.3、x64win   x86的k3CloudApp   x86第三方组件   x86的第三方依赖组件...;【6.0最新补丁支持Web登陆后下载x86的k3CloudApp】


如果第三方组件只有x86平台,那么请下载运行x86的客户端来匹配第三方组件:

image.webp


4,这个方案已经再好几家客户上线应用了;只要正确配置,一定可以运行;


答疑3】:

客制化控件已经编译完成,如何制作安装包?

【答】:

通过BOSIDE新建解决方案或应用方案,具体方法参考下面教程:

《协同平台之客户端插件扩展篇》【https://vip.kingdee.com/school/97996311328065024 】指引

制作好安装包后,一定要进行本地测试验证:

【验证步骤】

1、安装客户环境匹配的标准产品版本和补丁;

2、安装新构建的二开功能补丁安装包;

3、从桌面启动客户端程序快捷方式,并等待组件更新;

4、更新完成后检查【%Program Files %\Kingdee\K3Cloud\DeskClient\K3CloudClient\controlplugins】目录是否正确更新到组件;

5、登入系统执行相关二开业务功能,检验是否正常;

6、如二开功能需要修改重新打包,则验证过程需要从第一步开始再来一遍;

7、以上完成确认无误后,再在系统闲时部署到正式生产环境;


答疑4】:

如何使用MainDownloadList.xml注册文件配置?

【答】:这种模式为登录界面增量下载模式,在登录界面进行校验下载

一般推荐使用BOSIDE进行注册,或采用答疑3的方法在下载客户端时直接同步下载。

下面介绍MainDownloadList.xml注册文件配置:

1、运行记事本编辑器,打开安装目录【WebSite/ClientBin/CustomControl/WPF/MainDownloadList.xml】的文件。

手工拷贝一个节点(一个文件需要一个节点进行注册如下图)。然后修改绿色的文件名为你的组件名就好了。

c35f855a466841f3bdf3f827ddaaced5.webp


2、如果有组件需要更新,请拷贝相关组件到【WebSite/ClientBin/CustomControl/WPF】目录下,并执行上面3.1的步骤,注意更新组件的动作仅在客户端启动时运行一次。同时需要修改注册文件的版本号,以便客户端能够更新到最新组件,如下图:

e9b4b1b23afa4884bd5883180bfea53e.webp

3、kdz文件部署方法:

在K/3 Cloud服务器安装目录 的【WebSite/ClientBin/CustomControl/WPF】目录下部署二次开发组件,这个目录安装包会自动创建。自定义组件可以用Zip工具压缩为标准Zip格式,后修改后缀名为.kdz, 比如 a.dll -> a.dll.kdz。

3.1、每个组件包括引用组件单独压缩为zip格式的文件,例如:a.dll -> a.dll.zip;

3.2、修改后缀名为.kdz,例如:修改a.zip 文件为 a.dll.kdz;

3.3、配置配置文档,参见 2 或3.1

3.4、部署是注意IIS站点中是否存在MIME类型为.kdz的,如果没有,需要手工加入MIME类型如下:.kdz,application/x-msdownload  ;


答疑5】:

    如何使用代码方式配置自定义控件?

【答】:

    可以通过放置普通面板,修改xtype方式实现代码化配置自定义控件。编写构造插件方式实现客制化:(备选方案)

1、新建动态表单,拖入面板控件,命名为【FCustomControl】。

2、编写客制化控件服务端表单构建插件,如下代码:

    /// <summary>

    /// 测试自定义控件构造插件

    /// </summary>

    public class TestCustomControlCreatePlugIn : AbstractDynamicWebFormBuilderPlugIn

    {

        public override void CreateControl(CreateControlEventArgs e)

        {

            base.CreateControl(e);


            if (e.ControlAppearance.Key.EqualsIgnoreCase("FCustomControl"))

            {

                e.Control["Assembly"] = "Kingdee.XPF.CustomControlPlugins";

                e.Control["ClassName"] = "SmartCardReader";

                e.Control["NameSpace"] = "Kingdee.XPF.CustomControlPlugins";

                e.Control["SourcePath"] = "\\ClientBin\\CustomControl\\WPF\\Kingdee.XPF.CustomControlPlugins.dll";

                e.Control["xtype"] = "kdcustomcontroldef";

            }

        }

    }


答疑6】:

    如何调试自定义的二开控件??

【答】:

1、VS打开代码,并编译Debug版本的二开组件;

2、拷贝debug组件到部署目录【C:\Program Files (x86)\Kingdee\K3Cloud\DeskClient\K3CloudClient\controlplugins】;

3、在需要的二开代码位置设置断点;

4、启动云星空客户端程序;

5、附加主进程就可以开始调试了;

image.webp



答疑7】:

    插件中调用DoCustomMethod方法来执行控件方法,为何不成功?

image.webp

【答】:

调用定制控件接口不成功一般是以下情况,可以参考检查

1、不成功一般是方法名大小写不匹配;

2、参数数量不匹配;

3、参数类型不匹配;

4、上面案例可见是参数2类型有问题;

5、如果实在不想纠结类型,可以把接口都定义为string,然后进行参数校验即可;

6、调用方法再次说明:

    6.1、调用方法, 实现定制控件的自定义接口调用
    this.View.GetControl(控件标识ID).InvokeControlMethod("DoCustomMethod", "WriteString", args);
    6.2、参数说明,第一为方法名,第二为方法参数列表数组如下args说明

    // 这里的args参数对应的是一个对象数组,即控件接口参数列表,在C#中如下编码;
    //0个参数的情况: var args = new object[1]; args[0] = null;
    //1个参数的情况: var args = new object[1]; args[0] = str1;
    //2个参数的情况: var args = new object[2]; args[0] = str1; args[1] = str2;
    //n个参数的情况: var args = new object[n]; args[0] = str1; args[1] = str2; ... args[n-1] = strn;


答疑8】:

   文章中“ public class SmartCardReader : ContentControl, IKDCustomControl”的ContentControl没有找到,或者没有实现。

【答】:

请仔细参看文章,要求新建WPF类库工程。ContentControl 默认就包含在WPF类库组件System.Windows.Controls命名空间中了。

image.webp


答疑9】:

   如果只是串口的简单应用,需要写这个复杂的定制控件吗?

【答】:

   依据串口应用情况,可以参考以下建议:

1、普通标准串口简单应用,可以参考: 《串口外接设备支持--编程说明》 https://vip.kingdee.com/article/8842

2、存在sdk调用等复杂需求的,高级自定义控件访问本地资源:《客制化控件插件实现客户端设备集成 》https://vip.kingdee.com/article/8751





。。。。。。        

客制化控件 + 实现客户端设备集成 (仅支持到WPF的GUI客户端)

本篇基础:BOS平台的二开编程(论坛),C#编程(ms),WPF编程(ms),Python语法, XML语法,VistualStudio2012(up)工具(ms),C/S基础知识,x86...
点击下载文档
确认删除?
回到顶部
客服QQ
  • 客服QQ点击这里给我发消息