创建模型驱动应用字段组件

在本教程中,你将创建模型驱动应用field组件,并使用Visual Studio Code在窗体上部署、配置和测试组件。 此代码组件会在表单上显示一组选项,并在每个选项值旁边显示一个图标。 该组件使用模型驱动应用的某些高级功能,例如选择列定义(元数据)和列级安全性。

除了这些功能之外,请确保代码组件遵循最佳做法指南:

  1. 使用 Microsoft Fluent UI 实现一致性和可访问性
  2. 在设计和运行时中本地化代码组件标签。
  3. 确保代码组件是元数据驱动的,以提高可重用性。
  4. 确保代码组件根据外形和可用宽度进行呈现,并在空间受限时显示带图标的紧凑型下拉菜单。

ChoicePicker 模型驱动应用字段组件的屏幕截图,其中包含选择值旁边的图标。

下载 ChoicesPicker 示例代码

可以从 PowerApps-Samples/component-framework/ChoicesPickerControl/下载完整的示例。

创建新 pcfproj 项目

注释

在开始之前,请确保安装所有 必备组件

若要创建新 pcfproj 项目,请执行以下操作:

  1. 创建新文件夹以保存代码组件。 例如,C:\repos\ChoicesPicker

  2. 打开Visual Studio Code并转到“文件>打开文件夹”。 选择在上一步中创建的 ChoicesPicker 文件夹。 如果在安装Visual Studio Code期间添加了Windows资源管理器扩展,还可以在文件夹中使用“打开代码”上下文菜单选项。 当当前目录位于该位置时,还可以在命令提示符中使用 code . 将任意文件夹添加到 Visual Studio Code 中。

  3. 在新的 Visual Studio Code PowerShell 终端(终端>新终端)内,使用 pac pcf init 命令创建新的代码组件项目:

    pac pcf init `
       --namespace SampleNamespace `
       --name ChoicesPicker `
       --template field `
       --run-npm-install
    

    或使用短格式:

    pac pcf init -ns SampleNamespace -n ChoicesPicker -t field -npm
    

此步骤将新的 ChoicesPicker.pcfproj 和相关文件添加到当前文件夹,包括定义所需模块的一个 package.json 文件。 上述命令还会运行 npm install 命令,以安装所需的模块。

Running 'npm install' for you...

注释

如果收到错误 The term 'npm' is not recognized as the name of a cmdlet, function, script file, or operable program.,请确保安装 node.js (建议使用 LTS 版本)和其他所有先决条件。

创建 ChoicesPicker 代码组件的 pac pcf init 命令的屏幕截图。

可以看到模板包含一个 index.ts 文件以及各种配置文件。 此文件是代码组件的起点,包含 组件实现中所述的生命周期方法。

安装 Microsoft Fluent UI

使用 Microsoft Fluent UI 和 React 创建 UI,因此请安装这些依赖项。 若要安装依赖项,请使用:

npm install react react-dom @fluentui/react

此命令将模块添加到 packages.json 文件,并将其安装到 node_modules 文件夹中。 不要将 提交到源代码管理,因为之后可以使用 还原所有必需的模块。

Microsoft Fluent UI 的一个优点是它提供一致且高度可访问的 UI。

配置 eslint

pac pcf init 使用的模板将eslint模块安装到项目,并通过添加.eslintrc.json文件对其进行配置。 你需要为 TypeScript 和 React 的编码样式配置 eslint。 有关详细信息,请参阅 为代码组件配置 ESLint

编辑清单

该文件 ChoicesPicker\ControlManifest.Input.xml 定义描述代码组件行为的元数据。 控件属性已包含组件的命名空间和名称。

定义以下绑定属性和输入属性:

Name Usage 类型 说明
价值 bound OptionSet 将此属性链接到所选列。 代码组件接收当前值,然后在值更改时通知父上下文。
图标映射 input 多行文本 当应用创建者将代码组件添加到窗体时,此属性将设置其值。 它包含一个 JSON 字符串,用于配置可用于每个选择值的图标。

有关详细信息,请参阅 属性元素

Tip

通过设置 XML 格式,可以更轻松地阅读 XML,以便属性显示在单独的行上。 在 Visual Studio Code 市场中查找并安装所选的 XML 格式设置工具: 搜索 xml 格式设置扩展

以下示例已使用单独的行上的属性进行格式化,使其更易于阅读。

将现有的 sampleProperty 替换为新的属性

ChoicesPicker\ControlManifest.Input.xml控件元素中打开并粘贴以下属性定义,替换现有sampleProperty属性:

<property name="sampleProperty"
  display-name-key="Property_Display_Key"
  description-key="Property_Desc_Key"
  of-type="SingleLine.Text"
  usage="bound"
  required="true" />

保存更改,然后使用以下命令生成组件:

npm run build

生成组件后,会看到:

  • 自动生成的文件 ChoicesPicker\generated\ManifestTypes.d.ts 将添加到项目中。 生成过程从 ControlManifest.Input.xml 中生成此文件,并提供用于与输入/输出属性交互的类型。

  • 生成输出将添加到 out 文件夹。 bundle.js 是在浏览器中运行的转译后的 JavaScript。 ControlManifest.xml是部署期间使用的 ControlManifest.Input.xml 文件的重新格式化版本。

    注释

    请勿直接修改 outgenerated 文件夹中的内容。 构建过程会覆盖这些内容。

实现 ChoicesPicker Fluent UI React 组件

当代码组件使用 React 时,该方法 updateView 必须呈现单个根组件。 在该 ChoicesPicker 文件夹中,添加新的 ChoicesPickerComponent.tsxTypeScript 文件,并添加以下内容:

import { ChoiceGroup, IChoiceGroupOption } from '@fluentui/react/lib/ChoiceGroup';
import * as React from 'react';

export interface ChoicesPickerComponentProps {
    label: string;
    value: number | null;
    options: ComponentFramework.PropertyHelper.OptionMetadata[];
    configuration: string | null;
    onChange: (newValue: number | undefined) => void;
}

export const ChoicesPickerComponent = React.memo((props: ChoicesPickerComponentProps) => {
    const { label, value, options, configuration, onChange } = props;
    const valueKey = value != null ? value.toString() : undefined;
    const items = React.useMemo(() => {
        let iconMapping: Record<number, string> = {};
        let configError: string | undefined;
        if (configuration) {
            try {
                iconMapping = JSON.parse(configuration) as Record<number, string>;
            } catch {
                configError = `Invalid configuration: '${configuration}'`;
            }
        }

        return {
            error: configError,
            choices: options.map((item) => {
                return {
                    key: item.Value.toString(),
                    value: item.Value,
                    text: item.Label,
                    iconProps: { iconName: iconMapping[item.Value] },
                } as IChoiceGroupOption;
            }),
        };
    }, [options, configuration]);

    const onChangeChoiceGroup = React.useCallback(
        (ev?: unknown, option?: IChoiceGroupOption): void => {
            onChange(option ? (option.value as number) : undefined);
        },
        [onChange],
    );

    return (
        <>
            {items.error}
            <ChoiceGroup
                label={label}
                options={items.choices}
                selectedKey={valueKey}
                onChange={onChangeChoiceGroup}
            />
        </>
    );
});
ChoicesPickerComponent.displayName = 'ChoicesPickerComponent';

注释

该文件具有扩展名 tsx,即支持 React 使用的 XML 样式语法的 TypeScript 文件。 生成过程将其编译为标准 JavaScript。

ChoicesPickerComponent 设计说明

本部分包含关于 ChoicesPickerComponent 设计的评论。

它是一个功能组件

这是 React 功能组件,但同样,它可以是 类组件。 这是基于你的首选编码样式。 类组件和功能组件也可以在同一项目中混合。 函数和类组件都使用 tsx React 使用的 XML 样式语法。 详细信息: 函数和类组件

最小化 bundle.js 大小

使用路径式导入引入 ChoiceGroup Fluent UI 组件时,请避免以下写法:

import { ChoiceGroup, IChoiceGroupOption } from '@fluentui/react';

我们使用:

import { ChoiceGroup, IChoiceGroupOption } from '@fluentui/react/lib/ChoiceGroup';

这样,捆绑包大小就会更小,因此容量需求较低,运行时性能更好。

替代方案是采用树摇技术。

“属性”的说明

输入属性具有以下属性,这些属性将由 index.tsupdateView 方法中提供:

prop 说明
label 用于标记组件。 这与父上下文提供的元数据字段标签绑定,使用模型驱动应用中选定的 UI 语言。
value 链接到清单中定义的输入属性。 当记录为新记录或未设置字段时,这可以为 null。 使用 TypeScript null ,而不是 undefined 在传递/返回属性值时使用。
options 当代码组件绑定到模型驱动应用中的选项列时,该属性包含 OptionMetadata 描述可用选项的属性。 将此传递给组件,以便它可以呈现每个项。
configuration 组件的目的是显示每个可用选项的图标。 将代码组件添加到窗体时,应用创建者会提供配置。 此属性接受 JSON 字符串,该字符串将每个数字选择值映射到 Fluent UI 图标名称。 例如,{"0":"ContactInfo","1":"Send","2":"Phone"}
onChange 当用户更改选项选择时,React 组件将 onChange 触发事件。 然后,代码组件调用 notifyOutputChanged,以便模型驱动应用能够用新值更新该字段。

受控 React 组件

有两种类型的 React 组件:

类型 说明
不受控制 维护其内部状态,并将输入属性仅用作默认值。
受控 渲染由组件属性传递的值。 onChange如果事件未更新属性值,用户将不会在 UI 中看到更改。

ChoicesPickerComponent 是一个受控组件,因此,一旦模型驱动应用更新了该值(在调用 notifyOutputChanged 之后),它就会使用新值调用 updateView,然后该值会传递给组件的 props,从而触发重新渲染并显示更新后的值。

解构赋值

常量的赋值 propsconst { label, value, options, onChange, configuration } = props; 使用 析构赋值。 这样,您可以从 props 中提取呈现所需的属性,而无需在每次使用时都为它们加上props前缀。

React 组件和 Hook 的使用

下面将解释 ChoicesPickerComponent.tsx 如何使用 React 组件和 Hook:

物品 Explanation
React.memo 包裹我们的函数式组件,使其仅在任何输入属性发生变化时才进行渲染。
React.useMemo 为了确保所创建的项数组仅在输入属性 optionsconfiguration 发生变化时才会被修改。 对于函数组件来说,这是一种最佳实践,可以减少子组件不必要的重新渲染。
React.useCallback 创建一个回调闭包,当 Fluent UI ChoiceGroup 值发生变化时调用该闭包。 此 React Hook 确保仅当输入属性 onChange 发生变化时,回调闭包才会被修改。 这是类似于 useMemo的性能最佳做法。

配置输入属性的出错行为

如果 JSON 配置输入属性解析失败,则使用 items.error 呈现该错误。

更新index.ts以呈现 ChoicesPicker 组件

您需要更新生成的index.ts文件以渲染ChoicesPickerComponent

在代码组件中使用 React 时,该方法 updateView 将呈现根组件。 将渲染该组件所需的所有值都传递给该组件。 当这些值发生变化时,组件将重新渲染。

添加 import 语句并初始化图标

在文件中使用 ChoicesPickerComponent 组件 index.ts 之前,请在文件顶部添加以下代码:

import { IInputs, IOutputs } from "./generated/ManifestTypes";

注释

必须导入 initializeIcons ,因为使用的是 Fluent UI 图标集。 调用 initializeIcons 以加载测试工具中的图标。 在模型驱动应用中,图标已初始化。

向 ChoicesPicker 类添加属性

代码组件使用属性维护其实例状态。 此状态不同于 React 组件状态。 在 index.ts 文件中,将以下属性添加到 ChoicesPicker 类:

export class ChoicesPicker implements ComponentFramework.StandardControl<IInputs, IOutputs> {

下表说明了这些属性:

Attribute 说明
notifyOutputChanged 保留对用于通知模型驱动应用的方法的引用,即用户更改了选择值,并且代码组件已准备好将其传递回父上下文。
rootContainer 创建的 HTML DOM 元素用于在模型驱动应用中保存代码组件。
selectedValue 保存用户选择的选项的状态,以便可以在方法内 getOutputs 返回它。
context Power Apps组件框架上下文,用于读取清单和其他运行时属性中定义的属性,以及访问 API 方法,例如trackContainerResize

更新 init 方法

若要设置这些属性,请更新 init 方法。

public init(
    context: ComponentFramework.Context<IInputs>, 
    notifyOutputChanged: () => void, 
    state: ComponentFramework.Dictionary, 
    container: HTMLDivElement): 
    void {
    // Add control initialization code
}

init 代码组件在应用屏幕上初始化时调用该方法。

添加 onChange 方法

当用户更改所选值时,请从onChange事件调用notifyOutputChanged。 添加函数:

onChange = (newValue: number | undefined): void => {
     this.selectedValue = newValue;
     this.notifyOutputChanged();
};

更新 getOutputs 方法

public getOutputs(): IOutputs {
    return {};
}

Tip

如果在模型驱动应用中之前编写了客户端 API 脚本,则可以使用表单上下文更新属性值。 代码组件不应访问此上下文。 而应依靠 notifyOutputChangedgetOutputs 来提供一个或多个已更改的值。 无需返回接口中 IOutput 定义的所有绑定属性,只需返回更改其值的属性。

更新 updateView 方法

更新 updateView 方法以渲染 ChoicesPickerComponent

public updateView(context: ComponentFramework.Context<IInputs>): void {
    // Add code to update control view
}

你从 context.parameters.value 中获取标签和选项。 value.raw 提供所选的数字选项;如果未选择任何值,则提供 null

编辑 destroy 函数

销毁代码组件时清理资源:

public destroy(): void {
    // Add code to cleanup control if necessary
}

有关详细信息,请参阅 ReactDOM.unmountComponentAtNode

启动测试框架

确保保存所有文件。 在终端,使用:

npm start watch

可以看到,测试框架一开始会在新浏览器窗口中渲染选择器。 最初,它显示错误,因为字符串属性 configuration 具有默认值 val。 设置配置,使其使用以下 Fluent UI 图标映射测试工具默认选项 0、1 和 2:

{"0":"ContactInfo","1":"Send","2":"Phone"}

ChoicesPicker 测试工具的屏幕截图,其中显示了选择图标和数据输入面板。

更改所选选项时,可在右侧 的“数据输入 ”面板中看到该值。 如果更改该值,组件会显示更新的关联值。

支持只读和列级安全

创建模型驱动应用的 field 组件时,当控件因列级安全性而处于只读或被屏蔽状态时,应用程序需要遵循该控件的状态。 如果代码组件在列为只读时未呈现只读界面,那么在某些情况下(例如记录处于非活动状态时),用户仍可更新本不应被更新的列。 有关更多信息,请参阅 使用列级安全性控制访问

编辑 updateView 方法以支持只读和列级安全性

index.ts 中,编辑 updateView 方法,添加以下代码以获取 disabledmasked 标志:

public updateView(context: ComponentFramework.Context<IInputs>): void {
    const { value, configuration } = context.parameters;
    if (value && value.attributes && configuration) {
        ReactDOM.render(
            React.createElement(ChoicesPickerComponent, {
                label: value.attributes.DisplayName,
                options: value.attributes.Options,
                configuration: configuration.raw,
                value: value.raw,
                onChange: this.onChange,
            }),
            this.rootContainer,
        );
    }
}

仅当列级安全配置应用于绑定列时,该 value.security 属性才在模型驱动应用中可用。

通过 props 将这些值传入 React 组件。

编辑 ChoicesPickerComponent 以添加已禁用和屏蔽的属性

ChoicesPickerComponent.tsx 中,通过将它们添加到 masked 接口来接受 ChoicesPickerComponentPropsdisabled 属性:

export interface ChoicesPickerComponentProps {
    label: string;
    value: number | null;
    options: ComponentFramework.PropertyHelper.OptionMetadata[];
    configuration: string | null;
    onChange: (newValue: number | undefined) => void;
}

编辑 ChoicesPickerComponent 的属性

将新属性添加到 props 中。

export const ChoicesPickerComponent = React.memo((props: ChoicesPickerComponentProps) => {
    const { label, value, options, configuration, onChange } = props;

编辑 ChoicesPickerComponent 的返回节点

ChoicesPickerComponent 内部,返回 React 节点时,使用这些新的输入属性来确保选择器处于禁用或遮罩状态。

return (
    <>
        {items.error}
        <ChoiceGroup
            label={label}
            options={items.choices}
            selectedKey={valueKey}
            onChange={onChangeChoiceGroup}
        />
    </>
);

注释

在测试框架中你应该不会看到任何差异,因为它无法模拟只读字段或列级安全。 在模型驱动应用程序中部署控件后,需要测试此功能。

使代码组件具有响应式效果

代码组件可以在 Web、平板电脑和移动应用上呈现。 请考虑可用空间。 在可用宽度受限时,使选项组件显示为下拉菜单。

导入下拉列表组件和图标

ChoicesPickerComponent.tsx 中,该组件使用 Fluent UI 的 Dropdown 组件来渲染小尺寸版本,因此你需要将其添加到导入项中:

import { ChoiceGroup, IChoiceGroupOption } from '@fluentui/react/lib/ChoiceGroup';
import * as React from 'react';

添加 formFactor 属性

更新代码组件,使其根据新的属性 formFactor 以不同方式渲染。 将以下属性添加到 ChoicesPickerComponentProps 接口:

export interface ChoicesPickerComponentProps {
  label: string;
  value: number | null;
  options: ComponentFramework.PropertyHelper.OptionMetadata[];
  configuration: string | null;
  onChange: (newValue: number | undefined) => void;
  disabled: boolean;
  masked: boolean;
}

将 formFactor 添加到 ChoicesPickerComponent 属性

formFactor 添加到 props 中。

export const ChoicesPickerComponent = React.memo((props: ChoicesPickerComponentProps) => {
    const { label, value, options, configuration, onChange, disabled, masked  } = props;

添加方法和修改以支持下拉组件

下拉列表组件需要不同的呈现方法。

  1. 在以下代码上方 ChoicesPickerComponent添加以下代码:

    const iconStyles = { marginRight: '8px' };
    
    const onRenderOption = (option?: IDropdownOption): JSX.Element => {
       if (option) {
           return (
             <div>
                 {option.data && option.data.icon && (
                   <Icon
                       style={iconStyles}
                       iconName={option.data.icon}
                       aria-hidden="true"
                       title={option.data.icon} />
                 )}
                 <span>{option.text}</span>
             </div>
           );
       }
       return <></>;
    };
    
    const onRenderTitle = (options?: IDropdownOption[]): JSX.Element => {
       if (options) {
           return onRenderOption(options[0]);
       }
       return <></>;
    };
    

    这些方法使 Dropdown 呈现下拉值旁边的正确图标。

  2. 添加新onChangeDropDown方法。

    onChange添加一个类似于ChoiceGroup事件处理程序的Dropdown方法。 在现有的 onChangeChoiceGroup 方法之后添加新的 Dropdown 版本:

    const onChangeDropDown = React.useCallback(
           (ev: unknown, option?: IDropdownOption): void => {
               onChange(option ? (option.data.value as number) : undefined);
           },
           [onChange],
       );
    

更改呈现的输出

进行以下更改以使用新 formFactor 属性。

return (
  <>
      {items.error}
      {masked && '****'}

      {!items.error && !masked && (
        <ChoiceGroup
            label={label}
            options={items.choices}
            selectedKey={valueKey}
            disabled={disabled}
            onChange={onChangeChoiceGroup}
        />
      )}
  </>
);

formFactor 较大时,输出 ChoiceGroup 组件;当它较小时,则使用 Dropdown

返回 DropdownOptions

你在 ChoicesPickerComponent.tsx 中最后需要做的一件事是,以与 ChoicesGroup 使用的方式略有不同的方式来映射选项元数据。 在 items 返回块中的现有 choices: options.map代码下,添加以下代码:

return {
    error: configError,
    choices: options.map((item) => {
      return {
          key: item.Value.toString(),
          value: item.Value,
          text: item.Label,
          iconProps: { iconName: iconMapping[item.Value] },
      } as IChoiceGroupOption;
    }),
};

编辑index.ts

既然选择组件会根据 formFactor 属性以不同方式渲染,请在 index.ts 内部的渲染调用中传递正确的值。

添加 SmallFormFactorMaxWidth 和 FormFactors 枚举

export class ChoicesPicker中的index.ts类之前添加以下代码。

const SmallFormFactorMaxWidth = 350;

const enum FormFactors {
  Unknown = 0,
  Desktop = 1,
  Tablet = 2,
  Phone = 3,
}

当组件开始使用 ChoiceGroup 而不是 SmallFormFactorMaxWidth 组件进行渲染时,Dropdown 表示其宽度。 FormFactors 枚举是为了在调用 context.client.getFormFactor 时提供便利而使用的。

添加代码以检测 formFactor

在现有属性下方,向 React.createElement 属性中添加以下代码:

React.createElement(ChoicesPickerComponent, {
    label: value.attributes.DisplayName,
    options: value.attributes.Options,
    configuration: configuration.raw,
    value: value.raw,
    onChange: this.onChange,
    disabled: disabled,
    masked: masked,
}),

请求调整大小的更新

由于您正在使用 context.mode.allocatedWidth,因此需要让模型驱动应用知道,当可用宽度发生变化时,您希望接收更新(通过调用 updateView 实现)。 在 init 方法中添加对 context.mode.trackContainerResize 的调用:

public init(
    context: ComponentFramework.Context<IInputs>, 
    notifyOutputChanged: () => void, 
    state: ComponentFramework.Dictionary, 
    container: HTMLDivElement): 
    void {
      this.notifyOutputChanged = notifyOutputChanged;
      this.rootContainer = container;
      this.context = context;
}

在测试框架中尝试

保存所有更改,以便测试工具浏览器窗口自动反映这些更改。 保持之前运行的 npm start watch 继续运行。 在 349350 之间切换组件容器宽度的值,并查看呈现行为不同。 将窗体因素网页手机之间切换,并观察相同的行为。

用于响应容器宽度和外形规格更改的 ChoicesPicker 测试工具的屏幕截图。

Localization

为了支持多种语言,代码组件可以包含一个资源文件,该文件为设计和运行时字符串提供翻译。

  1. 在位置 ChoicesPicker\strings\ChoicesPicker.1033.resx添加新文件。 要为其他区域设置添加标签,请将 1033 (en-us) 更改为您选择的区域设置。

  2. 使用Visual Studio Code资源编辑器,输入以下值:

    Name 价值
    ChoicesPicker_Name 选项选择器(模型驱动)
    ChoicesPicker_Desc 将选项显示为带有图标的选取器
    Value_Name 价值
    Value_Desc 用于绑定控件的选项字段
    Configuration_Name 图标映射配置
    Configuration_Desc 将选项值映射到 Fluent UI 图标的配置。 例如 {“1”:“ContactInfo”,“2”:“Send”}

    否则,请使用以下 XML 设置 .resx 文件的内容:

    <?xml version="1.0" encoding="utf-8"?>
    <root>
      <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
        <xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
        <xsd:element name="root" msdata:IsDataSet="true">
          <xsd:complexType>
            <xsd:choice maxOccurs="unbounded">
              <xsd:element name="metadata">
                <xsd:complexType>
                  <xsd:sequence>
                    <xsd:element name="value" type="xsd:string" minOccurs="0"/>
                  </xsd:sequence>
                  <xsd:attribute name="name" use="required" type="xsd:string"/>
                  <xsd:attribute name="type" type="xsd:string"/>
                  <xsd:attribute name="mimetype" type="xsd:string"/>
                  <xsd:attribute ref="xml:space"/>
                </xsd:complexType>
              </xsd:element>
              <xsd:element name="assembly">
                <xsd:complexType>
                  <xsd:attribute name="alias" type="xsd:string"/>
                  <xsd:attribute name="name" type="xsd:string"/>
                </xsd:complexType>
              </xsd:element>
              <xsd:element name="data">
                <xsd:complexType>
                  <xsd:sequence>
                    <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
                    <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
                  </xsd:sequence>
                  <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
                  <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
                  <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
                  <xsd:attribute ref="xml:space"/>
                </xsd:complexType>
              </xsd:element>
              <xsd:element name="resheader">
                <xsd:complexType>
                  <xsd:sequence>
                    <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
                  </xsd:sequence>
                  <xsd:attribute name="name" type="xsd:string" use="required"/>
                </xsd:complexType>
              </xsd:element>
            </xsd:choice>
          </xsd:complexType>
        </xsd:element>
      </xsd:schema>
      <resheader name="resmimetype">
        <value>text/microsoft-resx</value>
      </resheader>
      <resheader name="version">
        <value>2.0</value>
      </resheader>
      <resheader name="reader">
        <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
      </resheader>
      <resheader name="writer">
        <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
      </resheader>
      <data name="ChoicesPicker_Name" xml:space="preserve">
        <value>Choices Picker (Model Driven)</value>
        <comment/>
      </data>
      <data name="ChoicesPicker_Desc" xml:space="preserve">
        <value>Shows choices as a picker with icons</value>
        <comment/>
      </data>
      <data name="Value_Name" xml:space="preserve">
        <value>Value</value>
        <comment/>
      </data>
      <data name="Value_Desc" xml:space="preserve">
        <value>The choices field to bind the control to</value>
        <comment/>
      </data>
      <data name="Configuration_Name" xml:space="preserve">
        <value>Icon Mapping Configuration</value>
        <comment/>
      </data>
      <data name="Configuration_Desc" xml:space="preserve">
        <value>Configuration that maps the choice value to a fluent ui icon. E.g. {"1":"ContactInfo","2":"Send"}</value>
        <comment/>
      </data>
    </root>
    

    Tip

    请勿直接编辑 resx 文件。 Visual Studio Code资源编辑器或用于Visual Studio Code的扩展可简化此任务。

更新资源字符串清单

创建资源字符串后,更新 ControlManifest.Input.xml 以引用它们。

<?xml version="1.0" encoding="utf-8" ?>
<manifest>
  <control namespace="SampleNamespace"
    constructor="ChoicesPicker"
    version="0.0.1"
    display-name-key="ChoicesPicker"
    description-key="ChoicesPicker description"
    control-type="standard">
    <external-service-usage enabled="false">
    </external-service-usage>
    <property name="value"
      display-name-key="Value"
      description-key="Value of the Choices Control"
      of-type="OptionSet"
      usage="bound"
      required="true"/>
    <property name="configuration"
      display-name-key="Icon Mapping"
      description-key="Configuration that maps the choice value to a fluent ui icon."
      of-type="Multiple"
      usage="input"
      required="true"/>
    <resources>
      <code path="index.ts"
        order="1"/>
    </resources>
  </control>
</manifest>

可以看到:

  1. 现在,display-name-keydescription-key 的值指向 resx 文件中的相应键。
  2. 元素中有 resources 一个附加条目,指示代码组件应从引用的文件加载资源。

如果需要在组件中使用的更多字符串,请将它们添加到 resx 文件中,然后使用 getString 在运行时加载字符串。 有关详细信息,请参阅 实现本地化 API 组件

注释

测试工具的一个限制是它不会加载资源文件。 若要全面测试组件,需要将组件部署到Microsoft Dataverse。

在模型驱动应用中部署和配置

使用测试工具测试基本功能后,将组件部署到Microsoft Dataverse,以便可以在模型驱动应用中端到端地测试代码组件。

  1. 在 Dataverse 环境中,确保创建一个发布者,其前缀为 samples

    Dataverse 表单的屏幕截图,用于添加带有 samples 前缀的解决方案发布者。

    你也可以使用你自己的发布者,只要在调用 pac pcf push 时相应地更新发布者前缀参数。

    有关详细信息,请参阅 创建解决方案发布者

  2. 保存发布者后,针对你的环境对 Microsoft Power Platform CLI 进行授权,以便推送已编译的代码组件。 在命令行中,使用:

    pac auth create --url https://myorg.crm.dynamics.com
    

    用你的 Dataverse 环境的 URL 替换 myorg.crm.dynamics.com。 出现提示时,使用系统管理员或定制员特权登录。 这些角色提供将任何代码组件部署到 Dataverse 所需的权限。

  3. 若要部署代码组件,请使用:

    pac pcf push --publisher-prefix samples
    

    注释

    如果收到错误 Missing required tool: MSBuild.exe/dotnet.exe,请在 Path 环境变量中添加 MSBuild.exe/dotnet.exe 或使用 Developer Command Prompt for Visual Studio Code。 必须安装以下任一项:适用于 Windows 和 Mac 的 Visual Studio 2019Visual Studio 2019 生成工具。 确保按照先决条件中的说明选择 .NET build tools 工作负载。

  4. 该过程完成后,它会在环境中创建名为 PowerAppTools_samples 的临时解决方案。 ChoicesPicker 代码组件已添加到此解决方案中。 如有必要,可以稍后将代码组件移到解决方案中。 有关详细信息,请参阅代码组件应用程序生命周期管理(ALM)。

包含 ChoicesPicker 组件的PowerAppTools_samples临时解决方案的屏幕截图。

  1. 接下来,通过在经典编辑器中转到主表单,选择首选联系方式>更改属性>控件选项卡>,将代码组件添加到联系人表单中添加控件>选择“选项选择器”>添加

    注释

    将来,无需经典编辑器在模型驱动应用窗体上配置代码组件。

  2. 在组件上设置以下属性:

    • 将选项选取器设置为 Web、手机和平板电脑的默认值。

    • 通过单击编辑图标并选择绑定到静态值,在图标映射配置中输入以下字符串。

      {
          "1":"ContactInfo",
          "2":"Send", 
          "3":"Phone",
          "4":"Fax",
          "5":"DeliveryTruck"
      }
      

      这些 Fluent UI 图标用于每个选择值。

      具有图标映射配置的 ChoicesPicker 控件属性的屏幕截图。

    • 选择显示选项卡,然后取消选中在窗体上显示标签,因为您已在选项选择器上方显示了该标签。

  3. 保存发布窗体。

  4. 在模型驱动应用中打开联系人记录,并确保已选择正确的表单。 你看到的是 ChoicesPicker 代码组件,而不是标准的下拉控件。 (你可能需要强制刷新页面,才能让该组件显示出来。)

    注释

    你可能会发现,与模型驱动应用相比,测试框架中的文本对齐方式略有不同。 之所以发生这种差异,是因为测试工具的 CSS 规则不同于模型驱动应用。 因此,在部署后始终全面测试代码组件。

部署到 Dataverse 后进行调试

如果需要对组件进行进一步更改,则无需每次部署。 请改用 调试代码组件 中所述的技术来创建 Fiddler AutoResponder ,以在运行时从本地文件系统 npm start watch 加载该文件。

注释

如果可以使用测试工具测试所有功能,则在部署到 Dataverse 后可能无需进行调试。 但是,在分发代码组件之前,始终在 Dataverse 内部署和测试。

AutoResponder 如下所示:

REGEX:(.*?)((?'folder'css|html)(%252f|\/))?SampleNamespace\.ChoicesPicker[\.\/](?'fname'[^?]*\.*)(.*?)$
C:\repos\ChoicesPicker\out\controls\ChoicesPicker\${folder}\${fname}

Fiddler AutoResponder 规则的屏幕截图,该规则加载了本地 ChoicesPicker 的构建输出。

你需要在当前浏览器会话中清空缓存并强制刷新,才能让浏览器加载到 AutoResponder 文件。 加载后,可以刷新浏览器,因为 Fiddler 将缓存控件标头添加到文件,以防止缓存它。

完成更改后,可以在清单中将补丁版本号递增,然后使用 pac pcf push 重新部署。

到目前为止,你部署的是一个尚未优化的开发版本,因此在运行时速度较慢。 编辑 文件后,您可以选择使用 ChoicesPicker.pcfproj 来部署优化后的构建版本。 在下方 OutputPath,添加以下内容:

<PcfBuildMode>production</PcfBuildMode>

使用 Microsoft Power Platform 进行应用程序生命周期管理 (ALM)
Power Apps组件框架 API 参考
创建您的第一个组件
调试代码组件