Treinamento
Roteiro de aprendizagem
Use advance techniques in canvas apps to perform custom updates and optimization - Training
Use advance techniques in canvas apps to perform custom updates and optimization
Não há mais suporte para esse navegador.
Atualize o Microsoft Edge para aproveitar os recursos, o suporte técnico e as atualizações de segurança mais recentes.
Boas-vindas à Estrutura de Temas e Associação de Dados MRTK3. Essa estrutura foi projetada para facilitar a criação de elementos visuais que podem ser preenchidos e atualizados de forma dinâmica no runtime por dados fornecidos de uma ou mais fontes de dados.
A associação de dados é o processo que estabelece uma conexão entre a UX (exibição) de um aplicativo e os dados que estão sendo apresentados (modelo). Se a associação possui configurações corretas e os dados fornecem notificações adequadas, quando os dados mudam de valor, os elementos que são associados a dados refletem as mudanças automaticamente.
Estruturas de associação de dados populares:
Para obter mais informações, consulte Visão geral da associação de dados – WPF.NET
Com suporte no momento:
Além do que já está disponível, as principais prioridades para mais funcionalidades incluem:
Keypath totalmente resolvido – um keypath completo e absoluto que mapeia para um objeto específico em uma Fonte de Dados. Para itens em uma coleção, essa é uma combinação do keypath totalmente resolvido para uma entidade de coleção e um keypath relativo (local) para um elemento de dados dessa entidade de coleção.
Mapeador do Keypath – mapeador de namespace opcional entre keypaths locais e nomes de campo da Fonte de Dados (por exemplo, "link" <–> "URL").
Tema – Uma fonte de dados que fornece um conjunto de vários ativos e estilos necessários para alcançar uma estética visual específica.
Posicionador do item – Um companheiro do DataConsumerCollection responsável por colocar itens visíveis em uma cena.
Pool de Objetos de Dados – Pré-fabricados instanciados e em espera prontos para serem preenchidos com dados para navegação de lista de baixo GC.
Listar Virtualização – Capacidade de popular, apresentar e navegar por listas de tamanho arbitrariamente grande.
Pré-busca preditiva – Pré-busca de dados e preenchimento de pré-fabricados de coleção para itens que podem ser vistos em breve por meio da rolagem/paginação.
Uma fonte de dados é qualquer conjunto gerenciado de dados de tipos arbitrários e complexidade que podem ser usados para preencher exibições de dados por meio de consumidores de dados. Os dados gerenciados por uma fonte de dados podem ser estáticos ou dinâmicos. Todas as alterações nos itens de dados serão relatadas a todos os consumidores de dados que se registraram para receber notificações de alteração.
Uma interface simples que tem um único método para recuperar uma fonte de dados. Isso foi projetado para permitir que um componente de script MonoBehavior seja descoberto automaticamente na hierarquia de objetos do jogo por componentes de consumidor de dados. Não é necessário implementar uma fonte de dados diretamente no próprio objeto do jogo. Isso é útil quando um MonoBehaviour existente deve derivar de outra classe e várias heranças impedem a derivação de DataSourceGOBase. Ele também permite que mais código não tenha dependências do Unity.
O MonoBehaviour DataSourceProviderSingleton
possibilita especificar uma fonte de dados que pode ser descoberta automaticamente mesmo que não esteja na mesma hierarquia GameObject que os DataConsumers que desejam escutá-la. Basta colocar DataSourceProviderSingleton
em qualquer lugar na cena e preencher a propriedade Data Sources
com quaisquer fontes de dados que devem ser descobertas pelos consumidores de dados. Como alternativa, os consumidores de dados orientarão seus pais a encontrar uma fonte de dados apropriada, o que implica que você pode colocar uma fonte de dados que forneça os dados desejados em qualquer lugar da cadeia responsável desses consumidores de dados.
Um caminho da chave é o mecanismo para identificar de forma exclusiva qualquer informação em uma fonte de dados.
Embora um caminho da chave possa ser qualquer identificador exclusivo por item de dados, todas as implementações atuais usam um especificador legível do usuário lógico que indica a posição de navegação dos dados de interesse em relação a todo o conjunto de dados estruturados. Ele é modelado no conceito de listas, dicionários e primitivos do Javascript. Os caminhos de chave são instruções Javascript sintaticamente corretas para acessar dados que podem ser representados no JSON. A vantagem dessa abordagem é que ela se correlaciona bem com ambos o JSON e o XML. Esses são os dois meios mais prevalentes de transferir informações de serviços de back-end.
Exemplo de caminhos de chave:
Considerando que um caminho de chave é uma cadeia de caracteres arbitrária sem taxonomia necessária, os especificadores de dados reais podem ser qualquer método de descrição de quais dados recuperar. O XPath do XML é um exemplo de um esquema de caminho de chave viável que funcionaria com fontes de dados. Desde que os principais caminhos fornecidos pelo consumidor de dados sejam consistentes com os caminhos de chave esperados pela fonte de dados, tudo funcionará. Além disso, Mapeadores de Caminho de Chave podem ser implementados para traduzir entre esquemas diferentes.
Resolver um caminho de chave significa combinar dois keypaths:
Isso possibilita tratar um subconjunto de dados de maneira que não importe em qual lugar de uma hierarquia de conjunto de dados maior, ele realmente exista. O uso mais importante desse recurso é descrever os dados de uma única entrada em uma lista sem se preocupar com qual entrada nessa lista a instância atual está referenciando.
Como um Caminho de chave "totalmente resolvido" é sempre gerado e consumido por um DataSource e nunca, ou pelo menos raramente, deve ser modificado por um DataConsumer ou outro componente externo, ele pode ter qualquer estrutura que faça sentido para o DataSource. Por exemplo, se um pré-fabricado mostrar uma entrada de lista para uma foto e o título, data tirada e outros atributos dela, o caminho de chave local no pré-fabricado poderá ter esta aparência:
Os caminhos de chave totalmente resolvidos para uma entrada de pré-fabricado em uma lista podem ter esta aparência:
Um Mapeador de Caminho de Chave permite que fontes de dados e consumidores de dados usem namespaces e convenções diferentes para caminhos de chave e ainda trabalhem juntos.
Um pré-fabricado para um elemento comumente usado, como uma imagem fixa para mostrar informações de contato de uma pessoa, pode conter campos variáveis gerenciados por consumidores de dados. Para tornar isso possível, o identificador usado para qualquer aspecto variável do pré-fabricado precisa de uma forma de mapear para o identificador para a referência correta na fonte de dados que, em cada uso do pré-fabricado, determinará o conteúdo desse elemento variável. O Mapeador de Caminho de Chave possibilita isso.
O pré-fabricado pode ser usado com diferentes fontes de dados em que os dados são armazenados em uma estrutura organizacional diferente e usa nomes de campo. Para usar um modelo de pré-fabricado com cada fonte de dados, um Mapeador de Caminho de Chave pode resolver quaisquer diferenças na forma como os dados são organizados.
Um objeto que sabe como consumir informações que estão sendo gerenciadas por uma fonte de dados e usar esses dados para preencher exibições de dados.
Os consumidores de dados podem se registrar com uma fonte de dados para serem notificados de quaisquer alterações em um item de dados existente em um caminho de chave especificado em um conjunto de dados. Sempre que os dados especificados forem alterados (ou houver a suspeita de terem sido alterados), os Consumidores de Dados serão notificados.
Uma Coleção de Consumidores de dados tem a capacidade adicional de gerenciar uma lista de itens semelhantes. Essa lista pode ser todo o conjunto de dados gerenciado por uma fonte de dados ou apenas um subconjunto. Normalmente, os dados de cada item na lista contêm tipos semelhantes de informações, mas isso não é um requisito. Fontes de dados e consumidores de dados podem dar suporte a listas aninhadas, como uma lista de palavras-chave associadas a cada foto em uma lista de fotos associadas a cada pessoa em uma lista de contatos. O caminho de chave para as palavras-chave seria relativo à foto, e o caminho de chave para as fotos seria relativo à pessoa, e o caminho-chave da pessoa seria relativo à lista responsável mais próxima ou à raiz do conjunto de dados.
Ao processar coleções, o keypath resolvido correto para a entrada específica na coleção é atribuído a cada consumidor de dados encontrado no pré-fabricado que é instanciado para cada item da coleção. Em seguida, isso é usado para resolver completamente o caminho da chave para qualquer dado de exibição relativo (local) dentro desse pré-fabricado.
Um consumidor de dados de coleção precisa de um meio para popular experiências do usuário com listas de elementos visuais repetíveis, como o que pode ser encontrado em uma lista rolável de produtos, fotos ou contatos. Isso é feito atribuindo um posicionador de item ao consumidor de dados de coleta. Esse posicionador de itens é a lógica que sabe como solicitar itens de lista, aceitar pré-fabricados que foram preenchidos com os dados variáveis e, em seguida, apresentá-los ao usuário, normalmente inserindo-os em uma lista gerenciada por um componente de layout de UX para listas.
O tema usa todo o encanamento de fontes de dados e consumidores de dados. É possível criar um tema em qualquer hierarquia de GameObjects se eles são estáticos ou são dinamicamente associados a outras fontes de dados. Isso permite que a associação de dados e o tema sejam aplicados em combinação. É possível até mesmo criar o tema dos dados provenientes de outra fonte de dados.
O tema é a capacidade de alterar a estética visual de vários elementos de UX ao mesmo tempo. Normalmente, todos os dados necessários para especificar um tema são fornecidos por uma única Fonte de Dados, como um Objeto Passível de Script. Também é possível que os dados de temas sejam fornecidos conforme necessário ou divididos em grupos lógicos com base na finalidade deles.
A associação de dados e o tema podem coexistir para um único elemento UX. Qualquer elemento UX individual pode ser simultaneamente temático e associado a dados. Nesse cenário, o fluxo típico é que a referência proveniente de um DataSource seja usada para derivar o keypath de tema correto. Esse keypath é usado para recuperar um objeto do tema Fonte de Dados, normalmente um perfil ScriptableObject, mas potencialmente qualquer fonte de dados que possa resolver um keypath.
Para simplificar a configuração de temas e associação de dados, é possível criar perfis de associação que são processados por um BindingConfigurator no momento da instanciação.
BindingConfigurator
processa um Perfil de Associação para determinar os ativos dentro de um pré-fabricado que devem ser temáticos e associa elementos de dados associados e elementos temáticos a Keypaths. Em seguida, ele também adiciona o DataConsumers
apropriado para associar esses elementos visuais aos seletores de Keypaths corretos que serão usados para referenciar dados específicos em um ou mais DataSources
, que normalmente são externos ao próprio pré-fabricado.DataSource
que contém dados para cada Keypath identificado no Perfil de Associação.ThemeProvider
facilita o uso de um ScriptableObject como um DataSource
para temas.MRTK_UX_ThemeProfile
ScriptableObject que está associado a um DataSourceReflection
no ThemeProvider
.Uma fonte de dados inserida é apropriada em duas situações:
Isso pode transformar qualquer struct ou classe C# em um DataSource
usando reflexão para mapear caminhos de chave para campos, propriedades, classes aninhadas, matrizes, listas ou dicionários. Ele pode ser associado a um ScriptableObject do Unity ou a qualquer outro struct C# ou classe em que os dados do tema existam. O objeto instanciado que contém os dados pode ser injetado por dependência e alterado em runtime.
Se os dados existirem como texto json
, isso gerenciará o mapeamento de keypaths para o DOM json
. Os ativos binários podem ser recuperados dos recursos do Unity, StreamingAssets ou até mesmo de uma URL buscada.
Essa é uma opção simples quando uma lista puramente simples é boa o suficiente para atender à necessidade e para uma prototipagem rápida. Todos os ativos de temas têm suporte, incluindo texto, ativos do Unity (por exemplo, Materiais, Sprites, Imagens), Recursos, StreamingAssets ou até mesmo busca externa por meio de uma URL.
Qualquer fonte de dados personalizada que implementa a interface direta IDataSource
ou deriva deDataSourceBase
ou DataSourceGOBase
pode ser usada para atender às necessidades personalizadas.
Os controles UXComponents padrão fornecidos no pacote UXComponents estão configurados para dar suporte a temas. Eles estão desligados por padrão, mas é fácil habilitá-los.
Cada controle, normalmente no GameObject mais alto do pré-fabricado raiz, tem um script chamado UXBindingConfigurator. Esse script, se habilitado, efetuará pull dos scripts de associação de dados necessários para ativar o tema. Certifique-se de importar o pacote de Associação de Dados e Temas também.
Observação sobre TextMeshPro StyleSheets: atualmente não é possível usar StyleSheets para estilizar o estilo TextMeshPro Normal. Qualquer outro estilo incluído na Folha de Estilos Padrão do TextMeshPro pode ser usado. Os exemplos usam Corpo para contornar essa limitação.
O MonoBehaviour DataSourceThemeProvider
pode ser usado para tornar facilmente um ScriptableObject contendo todas as referências a todos os ativos de temas que funcionam como uma fonte de dados. Isso é demonstrado na cena UXThemingExample.
O MonoBehaviour ThemeSelector
possibilita especificar e alternar facilmente entre vários perfis ScriptableObject. Um exemplo disso seria facilitar a alternância entre um tema "Escuro" e "Claro". Adicione o ScriptableObjects ao Theme Profiles
, normalmente em tempo de design. Em seguida, no tempo de execução, altere a propriedade Current Theme
para alterar o tema.
O tema é realizado pelos Consumidores de Dados, particularmente os que herdam das classes DataConsumerThemeBase<T>, DataConsumerTextStyle e DataConsumer personalizadas que qualquer desenvolvedor pode implementar para aprimorar o suporte a temas.
A classe base DataConsumerThemeBase<T> fornece lógica para usar um inteiro ou uma referência de chave de uma fonte de dados primária para pesquisar o valor final desejado de um banco de dados de tema secundário. Isso é feito mapeando os dados de entrada para um keypath de tema e, em seguida, usando esse keypath de tema para recuperar o valor final. Isso permite que qualquer elemento seja associado a dados e temático ao mesmo tempo. Por exemplo, imagine um campo de status em um banco de dados com status de Novo, Iniciado e Concluído representado pelos valores 0, 1 e 2. Cada um deles pode ser representado por um ícone sprite. Para a vinculação de dados, um valor de 0 a 2 é usado para pesquisar o sprite desejado. Com temas e vinculação de dados, o perfil de tema aponta para a lista correta de três sprites no perfil de tema e, em seguida, o valor de 0 a 2 é usado para selecionar o sprite correto nessa lista. Isso permite que o estilo desses ícones seja diferente por tema.
Quando o tema do runtime e a vinculação dinâmica de dados são usados juntos, uma classe DataConsumerThemeHelper pode ser especificada em qualquer classe derivada DataConsumerThemeBase para notificar quando um tema foi alterado.
A troca de temas no runtime é realizada substituindo os dados na fonte de dados do tema por um novo conjunto de dados disposto na mesma topologia do modelo de objeto de dados. DataSourceReflection pode ser usado com ScriptableObjects em que cada perfil representa um tema. Para todos os controles de UX do MRTK Core, o perfil de tema é um ScriptableObject chamado MRTK_UXComponents_ThemeProfile. O script auxiliar ThemeProvider.cs facilita o uso deste ou de qualquer perfil ScriptableObject como fonte de dados.
O método de aplicação de um tema a dados dinâmicos pode ser detectado automaticamente na maioria dos casos ou pode ser especificado explicitamente.
Quando a referência é usada para selecionar o item correto na fonte de dados do tema, o processo é:
O tipo de dados esperado da referência usada para recuperar o objeto desejado pode ser um dos seguintes:
Tipo de Dados | Descrição |
---|---|
Detecção Automática... | A referência é analisada e a interpretação correta é detectada automaticamente. Consulte "Tipo de Dados de Detecção Automática" abaixo para obter mais informações. |
DirectValue | Espera-se que a referência seja do tipo T desejado (por exemplo, Material, Sprite, Imagem) e usada diretamente. |
DirectLookup | Um índice integral ou uma chave de cadeia de caracteres usada para pesquisar o valor desejado de uma tabela de pesquisa local. |
StaticThemedValue | O objeto temático estático do tipo correto é recuperado da fonte de dados do tema no keypath de tema especificado. |
ThemeKeypathLookup | Um índice integral ou uma chave de cadeia de caracteres é usado para pesquisar o keypath de tema desejado. |
ThemeKeypathProperty | Um nome de propriedade de cadeia de caracteres que será acrescentada ao keypath base do tema fornecido no Tema. |
ResourcePath | Um caminho de recurso para recuperar o valor de um recurso do Unity (pode começar com "recurso://"). |
FilePath | Um caminho de arquivo para recuperar um ativo de streaming do Unity (pode começar com "arquivo://"). |
A detecção automática analisa os dados recebidos e decide o método de recuperação automaticamente. Na tabela abaixo, T representa o tipo desejado, como Material, Sprite, Imagem. A detecção automática pode ocorrer em dois locais no processo:
Tipo de referência | Considerações | Tem Auxiliar de Tema | Comportamento |
---|---|---|---|
T | N/D | S/N | Usada diretamente sem temas |
int | qualquer cadeia de caracteres integral primitiva ou passível de análise Int32 | Não | Passada como índice para GetObjectByIndex(n) derivado para recuperar o objeto Nth do tipo T. |
int | qualquer cadeia de caracteres integral primitiva ou passível de análise Int32 | Sim | Indexe para buscar o keypath de tema Nth da pesquisa local e, em seguida, recupere o objeto temático por meio da detecção automática. |
string | Formato: "resource://{resourcePath}" | S/N | resourcePath é usado para recuperar o recurso do Unity |
string | Format: "file://{filePath} | S/N | filePath é usado para recuperar um ativo de streaming |
string | Outro | Não | Passada como chave para GetObjectByKey() derivado para recuperar o objeto correspondente do tipo T. |
string | Outro | Sim | Chave para buscar o keypath de tema correspondente da pesquisa local e, em seguida, recuperar o objeto temático por meio da detecção automática. |
Um exemplo para recuperação de um ícone de status temático de um banco de dados que contém um valor de status numérico:
O tema é capaz de ativar folhas de estilos TMPro. O ScriptableObject "Configurações de TMP" determina onde se espera que as folhas de estilos estejam nos Recursos. É a propriedade "Ativo de Fonte Padrão => Caminho".
Certifique-se de colocar qualquer StyleSheets específico do aplicativo no mesmo sub-caminho fora de Recursos. Se você quiser organizá-las de forma diferente, atualize as "Configurações do TMP" para corresponder.
Se você estiver desenvolvendo novos controles de UX, é relativamente fácil torná-los temáticos. Na medida em que o controle usa Materiais, Sprites e outros ativos já em uso por outros controles de UX, geralmente é uma questão de nomear os vários objetos de jogo de maneira detectável.
É possível herdar do MRTK_UXCore_ThemeProfile
e adicionar campos mais temáticos ou apontar seus controles para seu próprio ScriptableObject. Não há nada de mais nos que são fornecidos além da organização do ScriptableObject que determinará os caminhos de chave necessários para acessar itens de dados individuais, por meio de Reflexão em C#.
Ao adicionar um script BindingConfigurator.cs ao nível superior do novo controle, você pode especificar seu próprio ScriptableObject BindingProfile serializado. Isso fornecerá o nome de GameObject necessário aos mapeamentos de KeyPath necessários para associar seus elementos temáticos com os dados fornecidos no perfil do tema. Esse script adicionará automaticamente todos os componentes necessários do DataConsumerXXX no runtime para dar suporte ao tema que você deseja usar.
Para uma primeira etapa, examine de perto as várias cenas de exemplo de associação de dados no pacote exemplos do MRTK e examine como os vários MonoBehaviours da fonte de dados são configurados. Em geral, os scripts de associação de dados só precisam ser colocados no GameObject de nível mais alto de um pré-fabricado ou um conjunto relacionado de elementos UX.
Além disso, para a maioria dos casos de uso, os valores padrão funcionam "fora da caixa", mas as propriedades expostas fornecem muita flexibilidade para os casos mais avançados.
Observação
Para habilitar o tema para os componentes padrão do MRTK UX, o símbolo MRTK_UX_DATABINDING_THEMING_ENABLED
deve ser definido nas Configurações do Player. Esse símbolo garante zero impacto no desempenho quando o tema não é necessário.
Esta cena que demonstra uma variedade de cenários de dados variáveis. Basta carregar a cena e reproduzir. Algumas observações:
O campo Entrada de Texto dos componentes TextMeshPro contém variáveis semelhantes a esta: {{ firstName }}. Esses marcadores são usados diretamente como keypaths locais.
Objetos de jogo para sprites e texto têm alguma forma de componente Consumidor de Dados que gerencia o recebimento de dados e a atualização de exibições.
Um único Consumidor de Dados pode ser compartilhado por vários componentes do mesmo tipo ao ser colocado mais alto na hierarquia GO.
Um Consumidor de Dados pode encontrar a própria Fonte de Dados, desde que esteja no mesmo objeto de jogo ou superior na hierarquia GO.
Um objeto de jogo pai tem um componente de Fonte de Dados que fornece dados para todos os objetos de jogo filho que apresentam um conjunto relacionado de informações variáveis.
Uma coleção Consumidor de Dados especifica um pré-fabricado que contém os consumidores de dados que serão usados para preencher esse pré-fabricado com dados variáveis.
Este exemplo usa temas para alternar AudioClips entre um conjunto para Piano e outro para Xylophone.
Este exemplo combina temas e associação de dados para mostrar um nível de bateria como um valor numérico e como um ícone. O tema é usado para selecionar entre um "tema de carregamento" e "não de carregamento". Ele foi projetado para atender aos seguintes objetivos:
ScriptableObject
como um perfil de tema.ScriptableObject
como um perfil de tema.Observação
A estrutura dessa demonstração não é um bom exemplo de combinação de temas e vinculação de dados. Em um aplicativo de produção para a separação adequada do modelo e da exibição, o estado real da bateria (nível e carregamento) seria fornecido em uma fonte de dados separada dos localizadores de recursos para os próprios sprites.
Este exemplo demonstra a alteração do tema de um aplicativo inteiro e também demonstra o uso de uma fonte de dados DataSourceGODictionary
para gerenciar uma variedade de conteúdo textual a ser exibido na UX. Em um cenário mais abrangente, os outros tipos de fonte de dados mais flexíveis provavelmente fornecerão a flexibilidade necessária, como DataSourceReflection
ou DataSourceGOJson
.
Aqui está um exemplo simples para ajudá-lo a começar de forma rápida:
{{ activity }}. It's {{ type }}.
Parabéns. Você criou seu primeiro projeto de Associação de Dados com o MRTK!
Uma fonte de dados fornece dados para um ou mais consumidores de dados. Seus dados podem ser qualquer coisa: gerados de algoritmos, em RAM, em disco ou recuperados de um banco de dados central.
Todas as fontes de dados precisam fornecer a interface IDataSource. Algumas das funcionalidades básicas são oferecidas em uma classe base chamada DataSourceBase
. Você provavelmente vai querer derivar dessa classe para adicionar a funcionalidade de gerenciamento de dados específica correspondente à sua necessidade.
Para tornar possível remover uma fonte de dados como um componente em um objeto de jogo, existe outro objeto base chamado DataSourceGOBase
, em que GO significa GameObject. Este é um MonoBehavior que pode ser descartado em um GameObject como um Componente. É um proxy fino que foi projetado para delegar o trabalho a uma fonte de dados principal não específica do Unity.
Uma fonte de dados pode expor a capacidade de manipular dados no Editor do Unity. Se esse for o caso, a classe derivada poderá conter toda a lógica da fonte de dados ou poderá aproveitar uma fonte de dados "stock", mas também adicionar campos de inspetor ou outros meios de configurar os dados.
Um consumidor de dados recebe notificações de alterações de dados e atualiza alguns aspectos da experiência do usuário, como o texto mostrado em um componente TextMeshPro.
Todos os consumidores de dados devem fornecer a interface IDataConsumer. Algumas das funcionalidades básicas são oferecidas em uma classe base chamada DataConsumerGOBase
, em que GO significa GameObject.
A maior parte do trabalho de um consumidor de dados é aceitar novos dados e prepará-los para apresentação. Isso pode ser tão simples quanto selecionar o pré-fabricado certo ou pode significar buscar mais dados de um serviço de nuvem, como um sistema de gerenciamento de conteúdo.
Um posicionador de item de coleta de dados é responsável por gerenciar quais partes de uma coleção estão visíveis no momento e como apresentar essa coleção visível, seja uma pequena lista estática ou um banco de dados gigante com milhões de registros.
Todos os posicionadores de item devem fornecer a interface IDataCollectionItemPlacer. Algumas das funcionalidades básicas são oferecidas em uma classe base chamada DataColletionItemPlacerGOBase
. Todos os posicionadores de item devem derivar dessa classe.
DataSource
.IDataNode
para serem interoperáveis com DataSourceObjects.Treinamento
Roteiro de aprendizagem
Use advance techniques in canvas apps to perform custom updates and optimization - Training
Use advance techniques in canvas apps to perform custom updates and optimization