Навчання
Навчальний шлях
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
Цей браузер більше не підтримується.
Замініть його на Microsoft Edge, щоб користуватися перевагами найновіших функцій, оновлень безпеки та технічної підтримки.
Welcome to the MRTK3 Data Binding and Theming framework. This framework is designed to make it easy to create visual elements that can be populated and updated dynamically at runtime by data provided from one or more data sources.
Data binding is the process that establishes a connection between an application's UX (view) and the data being presented (model). Suppose the binding has the correct settings and the data provides the proper notifications; when the data changes its value, the elements bound to the data reflect changes automatically.
Popular data binding frameworks:
For more information, see data binding overview - WPF.NET
Currently supported:
In addition to what's already available, top priorities for more capabilities include:
Fully Resolved Keypath - A full, absolute keypath that maps to one specific object in a Data Source. For items in a collection, this is a combination of the fully resolved keypath for one collection entity and a relative (local) keypath for one data element of that collection entity.
Keypath Mapper - Optional namespace mapper between local keypaths and Data Source field names (for example "link" <-> "URL").
Theme - A Data Source that provides a set of various assets and styles needed to achieve a specific visual aesthetic.
Item Placer - A DataConsumerCollection companion responsible for placing visible items into a scene.
Data Object Pool - Instantiated, standby prefabs ready to populate with data for low-GC list navigation.
List Virtualization - Ability to populate, present and navigate lists of arbitrarily large size.
Predictive Prefetch - Pre-fetching data and populating collection prefabs for items that may soon be seen by way of scrolling/paging.
A data source is any managed set of data of arbitrary type(s) and complexity that can be used to populate data views via data consumers. The data managed by a data source can be static or dynamic. Any changes to data items will be reported to any data consumers that have registered to receive change notifications.
A simple interface that has a single method to retrieve a data source. This is designed to allow a MonoBehavior scripting component to be auto-discovered in the game object hierarchy by data consumer components. There's no need to implement a data source directly on the game object itself. This is useful when an existing MonoBehaviour must derive from another class and multiple inheritance prevents deriving from DataSourceGOBase. It also allows more code to have no Unity dependencies.
The DataSourceProviderSingleton
MonoBehaviour makes it possible to specify a data source that can be automatically discovered even if it's not in the same GameObject hierarchy as the DataConsumers that wish to listen to it. Simply put the DataSourceProviderSingleton
anywhere in the scene and populate the Data Sources
property with any data sources that are to be discovered by data consumers. Alternatively, data consumers will walk their parents to find an appropriate data source, which implies that you can put a data source that provides the desired data anywhere in the parent chain of those data consumers.
A key path is the mechanism to uniquely identify any piece of information in a data source.
Although a key path can be any unique identifier per data item, current implementations use a logical user readable specifier that indicates the navigational position of the data of interest relative to the entire structured data set. It's modeled on Javascript's concept of lists, dictionaries and primitives. Key paths are syntactically correct Javascript statements for accessing data that can be represented in JSON. The advantage of this approach is that it correlates well with both JSON and XML. These are the two most prevalent means of transferring information from backend services.
Example key paths:
Given that a key path is an arbitrary string with no required taxonomy, the actual data specifiers could be any method of describing what data to retrieve. XML's XPath is an example of a viable key path schema that would work with data sources. As long as key paths provided by the data consumer are consistent with the keypaths expected by the data source, everything will work. Furthermore, Key Path Mappers can be implemented to translate between different schemas.
Resolving a key path means combining two keypaths:
This makes it possible to treat a subset of the data in such a way that it doesn't matter where in a larger data set hierarchy it actually exists. The most critical use of this ability is to describe the data of a single entry in a list without worrying about which entry in that list the current instance is referencing.
Since a "fully resolved" Key path is always generated and consumed by a DataSource and should rarely or never be modified by a DataConsumer or other external component, it can have any structure that makes sense to the DataSource. For example, if there's a prefab to show a list entry for a photo and its title, date taken and other attributes, the local key path in the prefab might look like this:
The fully resolved key paths for one prefab entry in a list might look like this:
A Key Path Mapper allows data sources and data consumers to use different namespaces and conventions for key paths and still work together.
A prefab for a commonly used element, such as a slate to show a person's contact information, can contain variable fields managed by data consumers. To make this possible, the identifier used for any variable aspect of the prefab needs a way to map to the identifier for the correct datum in the data source that will, in each use of the prefab, determine the contents of that variable element. The Key Path Mapper makes this possible.
The prefab may be used with different data sources where the data is stored in a different organizationl structure and uses field names. To use a template prefab with each data source, a Key Path Mapper can resolve any differences in how the data is organized.
An object that knows how to consume information being managed by a data source and use that data to populate data views.
Data Consumers can register with a data source to be notified of any changes to a data item that exists at a specified key path in a dataset. Whenever the data specified has changed (or is suspected of having changed), the Data Consumer(s) will be notified.
A Data Consumer Collection has the added ability to manage a list of similar items. This list can be the entire data set managed by a data source, or just a subset. Typically the data for each item in the list contains similar types of information, but this is not a requirement. Data sources and data consumers can support nested lists, such as a list of keywords associated with each photo in a list of photos associated with each person in a contact list. The keypath for the keywords would be relative to the photo, and the keypath for the photos would be relative to the person, and the keypath of the person would be relative to either the nearest parent list, or the root of the data set.
When processing collections, the correct resolved keypath for the specific entry in the collection is assigned to each data consumer found in the prefab that's instantiated for each collection item. That is then used to fully resolve the key path for any relative (local) view data within that prefab.
A collection data consumer needs a means to populate user experiences with lists of repeating visual elements, such as what might be found in a scrollable list of products, photos, or contacts. This is accomplished by assigning an item placer to the collection data consumer. This item placer is the logic tha knows how to request list items, accept prefabs that have been populated with variable data, and then present them to the user, typically by inserting them into a list managed by a UX layout component for lists.
Theming uses all of the plumbing of data sources and data consumers. It's possible to theme any hierarchy of GameObjects whether they're static or are dynamically data-bound to other data sources. This allows for both data binding and theming to be applied in combination. It's even possible to theme the data coming from another data source.
Theming is the ability to change the visual aesthetic of many UX elements at once. Typically, all of the data needed to specify a theme is provided by a single Data Source such as a Scriptable Object. It's also possible for theming data to be provided as needed or divided into logical groups based on its purpose.
Data Binding and Theming can co-exist for a single UX element. Any individual UX element can be simultaneously themed and data-bound. In this scenario, the typical flow is that the datum coming from a DataSource is used to derive the correct theme keypath. This keypath is then used to retrieve an object from the theme Data Source, typically a ScriptableObject profile, but potentially any source of data that can resolve a keypath.
To simplify configuration of theming and data binding, it's possible to create binding profiles that are processed by a BindingConfigurator at instantiation time.
BindingConfigurator
processes a Binding Profile to determine the assets within a prefab that are to be themed and associates both bound data elements and themable elements with Keypaths. It then adds appropriate DataConsumers
to bind these visual elements to the correct Keypaths selectors that will be used to reference specific data in one or more DataSources
, which are typically external to the prefab itself.DataSource
that contains data for each Keypath identified in the Binding Profile.ThemeProvider
helper script makes it easy to use a ScriptableObject as a DataSource
for theming.MRTK_UX_ThemeProfile
ScriptableObject that is bound to a DataSourceReflection
in the ThemeProvider
.An embedded data source is appropriate in two situations:
This can turn any C# struct or class into a DataSource
by using reflection to map keypaths to fields, properties, nested classes, arrays, lists or dictionaries. It can be associated with a Unity ScriptableObject or any other C# struct or class where theme data exists. The instantiated object containing the data can be dependency-injected and changed at runtime.
If the data exists as json
text, then this manages mapping keypaths to the json
DOM. Binary assets can be retrieved from Unity's Resources, StreamingAssets or even a fetched URL.
This is a simple option when a purely flat list is good enough to meet the need, and for rapid prototyping. All theming assets are supported including text, Unity assets (for example, Materials, Sprites, and Images), Resources, StreamingAssets, or even externally fetchable via a URL.
Any custom data source that implements the straightforward IDataSource
interface or derives from DataSourceBase
or DataSourceGOBase
can be used to meet custom needs.
The standard UXComponents controls provided in the UXComponents package are all configured to support theming. It's turned OFF by default, but is easy to enable.
Each control, typically on the topmost GameObject of the root prefab, has a script called UXBindingConfigurator. This script, if enabled, will pull in the needed data binding scripts to turn on theming. Make sure to import the Data Binding and Theming package as well.
Note on TextMeshPro StyleSheets: It's not currently possible to use StyleSheets to style the TextMeshPro Normal style. Any other style that's included in TextMeshPro's Default Style Sheet can be used. The examples use Body to work around this limitation.
The DataSourceThemeProvider
MonoBehaviour can be used to easily make a ScriptableObject containing all references to all theming assets function as a data source. This is demonstrated in the UXThemingExample scene.
The ThemeSelector
MonoBehaviour makes it possible to specify and easily swap between multiple ScriptableObject profiles. An example use of this would be to make it easy to switch between a 'Dark' and a 'Light' theme. Add the ScriptableObjects to the Theme Profiles
, typically at design time. Then, at run time, change the Current Theme
property to change the theme.
Theming is accomplished by Data Consumers, particularly ones that inherit from DataConsumerThemeBase<T>, DataConsumerTextStyle and custom DataConsumer classes that any developer can implement to enhance the theming support.
The DataConsumerThemeBase<T> base class provides logic to use an integer or key datum from a primary data source to look up the desired final value from a secondary theme database. This is accomplished by mapping the input data to a theme keypath and then using that theme keypath to retrieve the final value. This allows for any element to be both data-bound and themed at the same time. As an example, imagine a status field in a database with statuses of New, Started, and Done represented by values 0, 1 and 2. Each of these can be represented by a Sprite icon. For data binding, a value from 0 to 2 is used to look up the desired sprite. With theming and data binding, the theme profile points to the correct list of three sprites in the theme profile and then the value from 0 to 2 is used to select the correct sprite from that list. This allows the styling of these icons to differ per theme.
When both runtime theming and dynamic data binding are used together, a DataConsumerThemeHelper class can be specified in any DataConsumerThemeBase-derived class to notify when a theme has changed.
Swapping themes at runtime is accomplished by replacing the data at the theme data source with a new data set laid out in the same data object model topology. DataSourceReflection can be used with ScriptableObjects where each profile represents a theme. For all MRTK Core UX controls, the theme profile is a ScriptableObject named MRTK_UXComponents_ThemeProfile. The ThemeProvider.cs helper script makes it easy to use this or any ScriptableObject profile as a Data Source.
The method of applying a theme to dynamic data can be automatically detected in most cases, or it can be explicitly specified.
When the datum is used to select the correct item from the theme data source, the process is:
The expected data type of the datum used to retrieve the desired object can be one of the following:
Data Type | Description |
---|---|
AutoDetect | The datum is analyzed and the correct interpretation is automatically detected. See "Auto-detect Data Type" below for more information. |
DirectValue | The datum is expected to be of desired type T (for example, Material, Sprite, Image) and used directly. |
DirectLookup | An integral index or string key used to look up the desired value from a local lookup table. |
StaticThemedValue | Static themed object of the correct type is retrieved from the theme data source at specified theme keypath. |
ThemeKeypathLookup | An integral index or string key is used to look up the desired theme keypath. |
ThemeKeypathProperty | A string property name that will be appended to the theme base keypath provided in the Theme. |
ResourcePath | A resource path for retrieving the value from a Unity resource (may begin with "resource://"). |
FilePath | A file path for retrieving a Unity streaming asset (may begin with "file://"). |
Autodetect analyzes the data received and decides the retrieval method automatically. In the table below, T represents the desired type such as Material, Sprite, Image. Autodetect can occur at two places in the process:
Datum Type | Considerations | Has Theme Helper | Behavior |
---|---|---|---|
T | n/a | Y/N | Used directly with no theming |
int | any integral primitive or Int32 parsable string | No | Passed as index to derived GetObjectByIndex(n) to retrieve Nth object of type T. |
int | any integral primitive or Int32 parsable string | Yes | Index to fetch Nth theme keypath from local lookup and then retrieve themed object via auto-detect. |
string | Format: "resource://{resourcePath}" | Y/N | resourcePath is used to retrieve Unity Resource |
string | Format: "file://{filePath} | Y/N | filePath is used to retrieve a streaming asset |
string | Other | No | Passed as key to derived GetObjectByKey() to retrieve matching object of type T. |
string | Other | Yes | Key to fetch matching theme keypath from local lookup and then retrieve themed object via auto-detect. |
An example for retrieving a themed status icon from a database containing a numeric status value:
Theming is able to activate TMPro stylesheets. "TMP Settings" ScriptableObject dictates where stylesheets are expected to be in the Resources. It's the "Default Font Asset => Path" property.
Make sure to place any app specific StyleSheets in the same sub-path off of Resources. If you wish to organize them differently, make sure to update "TMP Settings" to match.
If you're developing new UX controls, it's relatively easy to make them themable. To the extent that the control uses Materials, Sprites, and other assets already in use by other UX controls, it's generally a matter of naming the various game objects in a discoverable way.
It's possible to inherit from MRTK_UXCore_ThemeProfile
and add more themable fields, or point your controls to your own ScriptableObject. There's nothing magical about the ones provided; the organization of the ScriptableObject will determine the keypaths needed to access individual data items by way of via C# Reflection.
By adding a BindingConfigurator.cs script to the top level of the new control, you can then specify your own serialized BindingProfile ScriptableObject. This will provide the necessary GameObject name to KeyPath mappings needed to associate your themable elements with the data provided in the theme profile. This script will automatically add any needed DataConsumerXXX components at runtime to support the theming you wish to use.
For a first step, take a close look at the various data binding example scenes in the MRTK Examples package and look at how the various data source MonoBehaviours are configured. In general, data binding scripts only need to be placed on the highest level GameObject of a prefab or a related set of UX elements.
Also, for most use cases, the default values work "out of the box," but the exposed properties provide a lot of flexibility for the more advanced cases.
Примітка
To enable theming for the standard MRTK UX components, the MRTK_UX_DATABINDING_THEMING_ENABLED
symbol must be defined in Player Settings. This symbol ensures zero performance impact when theming is not needed.
This scene that demonstrates a variety of variable data scenarios. Simply load the scene and play. A few things to notice:
The Text Input field of TextMeshPro components contains variables that look like this: {{ firstName }}. These markers are used directly as local keypaths.
Game objects for sprites and text have some form of Data Consumer component that manages receiving data and updating views.
A single Data Consumer can be shared by multiple components of the same type by being placed higher in the GO hierarchy.
A Data Consumer can find its own Data Source so long as it's on the same game object or higher in the GO hierarchy.
A parent game object has a Data Source component that provides data for all child game objects that present a related set of variable information.
A collection Data Consumer specifies a prefab which itself contains data consumers that will be used to populate that prefab with variable data.
This example uses theming to switch AudioClips between a set for Piano and one for Xylophone.
This example combines theming and data binding to show a battery level both as a numeric value and as an icon. Theming is used to select between a "charging them" and a "not charging" theme. It's designed to meet the following objectives:
ScriptableObject
acting as a theme profile.ScriptableObject
acting as a theme profile.Примітка
The structure of this demo is not a good example of combining theming and data binding. In a production application for proper separation of model and view, the actual battery state (level and charging) would be provided in a separate data source than the resource locators for the sprites themselves.
This example demonstrates changing the theme of an entire application and also demonstrates using a DataSourceGODictionary
as a data source for managing a variety of textual content to be displayed in the UX. In a more comprehensive scenario, the other more flexible data source types are likely to provide the needed flexibility, such as DataSourceReflection
or DataSourceGOJson
.
Here's a simple example to help you get started quickly:
{{ activity }}. It's {{ type }}.
Congratulations. You've created your first Data Binding project with MRTK!
A data source provides data to one or more data consumers. Its data can be anything: algorithmically generated, in RAM, on disk, or fetched from a central database.
All data sources must provide the IDataSource interface. Some of the basic functionality is offered in a base class called DataSourceBase
. You most likely want to derive from this class to add the specific data management functionality specific to your need.
To make it possible to drop a data source as a component onto a game object, another base object exists called DataSourceGOBase
where GO stands for GameObject. This is a MonoBehavior that can be dropped onto a GameObject as a Component. It's a thin proxy that's designed to delegate work to a non-Unity specific core data source.
A data source may expose the ability to manipulate data within Unity Editor. If this is the case, the derived class can contain all of the data source logic, or it can leverage a "stock" data source, but also add Inspector fields or other means of configuring the data.
A data consumer gets notifications when data has changed and then updates some aspect of the user experience, such as the text shown in a TextMeshPro Component.
All data consumers must provide the IDataConsumer interface. Some of the basic functionality is offered in a base class called DataConsumerGOBase
, where GO stands for GameObject.
The majority of the work of a data consumer is to accept new data and then prepare it for presentation. This may be as simple as selecting the right prefab, or it could mean fetching more data from a cloud service such as a content management system.
A data collection item placer is responsible for managing which parts of a collection are currently visible and how to present that visible collection, whether the collection is a small static list or a giant million record database.
All item placers must provide the IDataCollectionItemPlacer interface. Some of the basic functionality is offered in a base class called DataColletionItemPlacerGOBase
. All item placers should derive from this class.
DataSource
.IDataNode
interface to be interoperable with DataSourceObjects.Навчання
Навчальний шлях
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
Документація
MRTK3 core overview
MRTK3 audio overview
Standard assets overview - MRTK3
Mixed Reality Toolkit 3 for developers - Standard assets overview.