Native UI Components · React Native for Windows + macOS (original) (raw)
Attributes
Attribute | Use |
---|---|
ViewManagerExportedViewConstant | Specifies a field or property that represents a constant. |
ViewManagerProperty | Specifies a method to be called to set a property on an instance of a native UI widget. |
ViewManagerCommand | Specifies a method that can be called on an instance of a native UI widget. |
For this sample, assume we have the following CustomUserControl
that we want to use in React Native.
CustomUserControl.cs
:
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace ViewManagerSample
{
public sealed class CustomUserControl : Control
{
public static DependencyProperty LabelProperty { get; private set; }
public string Label
{
get
{
return (string)GetValue(LabelProperty);
}
set
{
SetValue(LabelProperty, value);
}
}
static CustomUserControl()
{
LabelProperty = DependencyProperty.Register(
nameof(Label),
typeof(string),
typeof(CustomUserControl),
new PropertyMetadata(default(string))
);
}
public CustomUserControl()
{
DefaultStyleKey = typeof(CustomUserControl);
}
}
}
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ViewManagerSample">
<Style TargetType="local:CustomUserControl" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomUserControl">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock Foreground="{TemplateBinding Foreground}" Text="{TemplateBinding Label}" TextAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
1. Authoring your View Manager
Here is a sample view manager written in C# called CustomUserControlViewManager
.
CustomUserControlViewManager.cs
:
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Controls;
using Microsoft.ReactNative.Managed;
using System.Collections.Generic;
namespace ViewManagerSample
{
internal class CustomUserControlViewManager : AttributedViewManager<CustomUserControl>
{
[ViewManagerProperty("label")]
public void SetLabel(CustomUserControl view, string value)
{
if (null != value)
{
view.Label = value;
}
else
{
view.ClearValue(CustomUserControl.LabelProperty);
}
}
[ViewManagerProperty("color")]
public void SetColor(CustomUserControl view, Brush value)
{
if (null != value)
{
view.Foreground = value;
}
else
{
view.ClearValue(Control.ForegroundProperty);
}
}
[ViewManagerProperty("backgroundColor")]
public void SetBackgroundColor(CustomUserControl view, Brush value)
{
if (null != value)
{
view.Background = value;
}
else
{
view.ClearValue(Control.BackgroundProperty);
}
}
[ViewManagerCommand]
public void CustomCommand(CustomUserControl view, IReadOnlyList<object> commandArgs)
{
// Execute command
}
}
}
2. Registering your View Manager
As with native modules, we want to register our new CustomUserControlViewManager
with React Native so we can actually use it. To do this, first we're going to create a ReactPackageProvider
which implements Microsoft.ReactNative.IReactPackageProvider.
ReactPackageProvider.cs
:
using Microsoft.ReactNative.Managed;
namespace ViewManagerSample
{
public partial class ReactPackageProvider : IReactPackageProvider
{
public void CreatePackage(IReactPackageBuilder packageBuilder)
{
CreatePackageImplementation(packageBuilder);
}
/// <summary>
/// This method is implemented by the C# code generator
/// </summary>
partial void CreatePackageImplementation(IReactPackageBuilder packageBuilder);
}
}
Here we've implemented the CreatePackage
method, which receives packageBuilder
to build contents of the package.
Now that we have the ReactPackageProvider
, it's time to register it within our ReactApplication
. We do that by simply adding the provider to the PackageProviders
property.
App.xaml.cs
:
using Microsoft.ReactNative;
namespace SampleApp
{
sealed partial class App : ReactApplication
{
public App()
{
/* Other Init Code */
PackageProviders.Add(new Microsoft.ReactNative.Managed.ReactPackageProvider()); // Includes any modules in this project
PackageProviders.Add(new ViewManagerSample.ReactPackageProvider());
/* Other Init Code */
}
}
}
This example assumes that the ViewManagerSample.ReactPackageProvider
we created above is in a different project (assembly) than our application. However you'll notice that by default we also added a Microsoft.ReactNative.Managed.ReactPackageProvider
.
The Microsoft.ReactNative.Managed.ReactPackageProvider
is a convenience that makes sure that all native modules and view managers defined within the app project automatically get registered. So if you're creating your view managers directly within the app project, you won't actually want to define a separate ReactPackageProvider
.
More extensibility points
- In some scenarios, a view manager might need to have more context at view creation time in order to decide what kind of control to instantiate. This can be achieved by having the view manager implement the interface. The
CreateViewWithProperties
method can then access the properties set in JSX by inspecting thepropertyMapReader
.
-internal class CustomUserControlViewManager : AttributedViewManager<CustomUserControl> {
+internal class CustomUserControlViewManager : AttributedViewManager<CustomUserControl>, IViewManagerCreateWithProperties {
// rest of the view manager goes here...
+ // IViewManagerCreateWithProperties
+ public virtual object CreateViewWithProperties(Microsoft.ReactNative.IJSValueReader propertyMapReader) {
+ propertyMapReader.ReaderValue(out IDictionary<string, JSValue> propertyMap);
+ // create a XAML FrameworkElement based on properties in propertyMap
+ if (propertyMap.ContainsKey("foo)) {
+ return new Button();
+ } else {
+ return new TextBox();
+ }
+ }
}
+}
- Your view manager is also able to declare that it wants to be responsible for its own sizing and layout. This is useful in scenarios where you are wrapping a native XAML control. To do so, implement the
Microsoft.ReactNative.IViewManagerRequiresNativeLayout
interface:
-internal class CustomUserControlViewManager : AttributedViewManager<CustomUserControl> {
+internal class CustomUserControlViewManager : AttributedViewManager<CustomUserControl>, IViewManagerRequiresNativeLayout {
// rest of the view manager goes here...
+ // IViewManagerRequiresNativeLayout
+ virtual bool RequiresNativeLayout() { return true; }
For this sample, assume we already have the CustomUserControl
defined in the C# example.
1. Authoring your View Manager
Here is a sample view manager written in C++ called CustomUserControlViewManager
.
CustomUserControlViewManager.h
:
#pragma once
#include "pch.h"
#include "winrt/Microsoft.ReactNative.h"
namespace winrt::ViewManagerSample::implementation {
struct CustomUserControlViewManager : winrt::implements<
CustomUserControlViewManager,
winrt::Microsoft::ReactNative::IViewManager,
winrt::Microsoft::ReactNative::IViewManagerWithNativeProperties,
winrt::Microsoft::ReactNative::IViewManagerWithCommands,
winrt::Microsoft::ReactNative::IViewManagerWithExportedEventTypeConstants,
winrt::Microsoft::ReactNative::IViewManagerWithReactContext> {
public:
CustomUserControlViewManager() = default;
// IViewManager
winrt::hstring Name() noexcept;
winrt::Windows::UI::Xaml::FrameworkElement CreateView() noexcept;
// IViewManagerWithNativeProperties
winrt::Windows::Foundation::Collections::
IMapView<winrt::hstring, winrt::Microsoft::ReactNative::ViewManagerPropertyType>
NativeProps() noexcept;
void UpdateProperties(
winrt::Windows::UI::Xaml::FrameworkElement const &view,
winrt::Microsoft::ReactNative::IJSValueReader const &propertyMapReader) noexcept;
// IViewManagerWithCommands
winrt::Windows::Foundation::Collections::IVectorView<winrt::hstring> Commands() noexcept;
void DispatchCommand(
winrt::Windows::UI::Xaml::FrameworkElement const &view,
winrt::hstring const &commandId,
winrt::Microsoft::ReactNative::IJSValueReader const &commandArgsReader) noexcept;
// IViewManagerWithExportedEventTypeConstants
winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomBubblingEventTypeConstants() noexcept;
winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomDirectEventTypeConstants() noexcept;
// IViewManagerWithReactContext
winrt::Microsoft::ReactNative::IReactContext ReactContext() noexcept;
void ReactContext(winrt::Microsoft::ReactNative::IReactContext reactContext) noexcept;
private:
winrt::Microsoft::ReactNative::IReactContext m_reactContext{ nullptr };
};
}
CustomUserControlViewManager.cpp
:
#include "pch.h"
#include "CustomUserControlViewManager.h"
#include "JSValueReader.h"
#include "JSValueXaml.h"
#include "NativeModules.h"
using namespace winrt;
using namespace Microsoft::ReactNative;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Controls;
namespace winrt::ViewManagerSample::implementation {
// IViewManager
hstring CustomUserControlViewManager::Name() noexcept {
return L"CustomUserControl";
}
FrameworkElement CustomUserControlViewManager::CreateView() noexcept {
return winrt::ViewManagerSample::CustomUserControl();
}
// IViewManagerWithNativeProperties
IMapView<hstring, ViewManagerPropertyType> CustomUserControlViewManager::NativeProps() noexcept {
auto nativeProps = winrt::single_threaded_map<hstring, ViewManagerPropertyType>();
nativeProps.Insert(L"label", ViewManagerPropertyType::String);
nativeProps.Insert(L"color", ViewManagerPropertyType::Color);
nativeProps.Insert(L"backgroundColor", ViewManagerPropertyType::Color);
return nativeProps.GetView();
}
void CustomUserControlViewManager::UpdateProperties(
FrameworkElement const &view,
IJSValueReader const &propertyMapReader) noexcept {
if (auto control = view.try_as<winrt::ViewManagerSample::CustomUserControl>()) {
const JSValueObject &propertyMap = JSValue::ReadObjectFrom(propertyMapReader);
for (auto const &pair : propertyMap) {
auto const &propertyName = pair.first;
auto const &propertyValue = pair.second;
if (propertyName == "label") {
if (propertyValue != nullptr) {
auto const &value = winrt::box_value(winrt::to_hstring(propertyValue.String()));
control.Label(value);
} else {
control.ClearValue(winrt::ViewManagerSample::CustomUserControl::LabelProperty());
}
} else if (propertyName == "color") {
if (auto value = propertyValue.To<Brush>()) {
control.Foreground(value);
} else {
control.ClearValue(Control::ForegroundProperty());
}
} else if (propertyName == "backgroundColor") {
if (auto value = propertyValue.To<Brush>()) {
control.Background(value);
} else {
control.ClearValue(Control::BackgroundProperty());
}
}
}
}
}
// IViewManagerWithCommands
IVectorView<hstring> CustomUserControlViewManager::Commands() noexcept {
auto commands = winrt::single_threaded_vector<hstring>();
commands.Append(L"CustomCommand");
return commands.GetView();
}
void CustomUserControlViewManager::DispatchCommand(
FrameworkElement const &view,
winrt::hstring const &commandId,
winrt::Microsoft::ReactNative::IJSValueReader const &commandArgsReader) noexcept {
if (auto control = view.try_as<winrt::ViewManagerSample::CustomUserControl>()) {
if (commandId == L"CustomCommand") {
const JSValueArray &commandArgs = JSValue::ReadArrayFrom(commandArgsReader);
// Execute command
}
}
}
// IViewManagerWithExportedEventTypeConstants
ConstantProviderDelegate CustomUserControlViewManager::ExportedCustomBubblingEventTypeConstants() noexcept {
return [](winrt::Microsoft::ReactNative::IJSValueWriter const& constantWriter) {
// use constantWriter to define bubbling events, see ExportedCustomDirectEventTypeConstants
}
}
ConstantProviderDelegate CustomUserControlViewManager::ExportedCustomDirectEventTypeConstants() noexcept {
return [](winrt::Microsoft::ReactNative::IJSValueWriter const& constantWriter) {
constantWriter.WritePropertyName(L"topMyEvent");
constantWriter.WriteObjectBegin();
WriteProperty(constantWriter, L"registrationName", L"onMyEvent");
constantWriter.WriteObjectEnd();
};
}
// IViewManagerWithReactContext
IReactContext CustomUserControlViewManager::ReactContext() noexcept {
return m_reactContext;
}
void CustomUserControlViewManager::ReactContext(IReactContext reactContext) noexcept {
m_reactContext = reactContext;
}
}
More extensibility points
- In some scenarios, a view manager might need to have more context at view creation time in order to decide what kind of control to instantiate. This can be achieved by having the view manager implement the interface:
struct CustomUserControlViewManager : winrt::implements<
CustomUserControlViewManager,
winrt::Microsoft::ReactNative::IViewManager,
winrt::Microsoft::ReactNative::IViewManagerWithNativeProperties,
winrt::Microsoft::ReactNative::IViewManagerWithCommands,
winrt::Microsoft::ReactNative::IViewManagerWithExportedEventTypeConstants,
+ winrt::Microsoft::ReactNative::IViewManagerCreateWithProperties,
winrt::Microsoft::ReactNative::IViewManagerWithReactContext> {
+ // IViewManagerCreateWithProperties
+ winrt::Windows::Foundation::IInspectable CreateViewWithProperties(winrt::Microsoft::ReactNative::IJSValueReader const &propertyMapReader);
The CreateViewWithProperties
method can then access the properties set in JSX by inspecting the propertyMapReader
just like it is done in the UpdateProperties
method.
- Your view manager is also able to declare that it wants to be responsible for its own sizing and layout. This is useful in scenarios where you are wrapping a native XAML control. To do so, implement the
winrt::Microsoft::ReactNative::IViewManagerRequiresNativeLayout
interface:
struct CustomUserControlViewManager : winrt::implements<
CustomUserControlViewManager,
winrt::Microsoft::ReactNative::IViewManager,
winrt::Microsoft::ReactNative::IViewManagerWithNativeProperties,
winrt::Microsoft::ReactNative::IViewManagerWithCommands,
winrt::Microsoft::ReactNative::IViewManagerWithExportedEventTypeConstants,
+ winrt::Microsoft::ReactNative::IViewManagerRequiresNativeLayout,
winrt::Microsoft::ReactNative::IViewManagerWithReactContext> {
+ // IViewManagerRequiresNativeLayout
+ bool RequiresNativeLayout() { return true; }
2. Registering your View Manager
As with native modules, we want to register our new CustomUserControlViewManager
with React Native so we can actually use it. To do this, first we're going to create a ReactPackageProvider
which implements Microsoft.ReactNative.IReactPackageProvider.
ReactPackageProvider.idl
:
namespace ViewManagerSample
{
[webhosthidden]
[default_interface]
runtimeclass ReactPackageProvider : Microsoft.ReactNative.IReactPackageProvider
{
ReactPackageProvider();
};
}
After that we add the .h and .cpp files:
ReactPackageProvider.h
:
#pragma once
#include "ReactPackageProvider.g.h"
using namespace winrt::Microsoft::ReactNative;
namespace winrt::ViewManagerSample::implementation
{
struct ReactPackageProvider : ReactPackageProviderT<ReactPackageProvider>
{
ReactPackageProvider() = default;
void CreatePackage(IReactPackageBuilder const& packageBuilder) noexcept;
};
}
namespace winrt::ViewManagerSample::factory_implementation
{
struct ReactPackageProvider : ReactPackageProviderT<ReactPackageProvider, implementation::ReactPackageProvider> {};
}
ReactPackageProvider.cpp
:
#include "pch.h"
#include "ReactPackageProvider.h"
#include "ReactPackageProvider.g.cpp"
#include <ModuleRegistration.h>
// NOTE: You must include the headers of your native modules here in
// order for the AddAttributedModules call below to find them.
#include "CustomUserControlViewManager.h"
using namespace winrt::Microsoft::ReactNative;
namespace winrt::ViewManagerSample::implementation {
void ReactPackageProvider::CreatePackage(IReactPackageBuilder const& packageBuilder)
noexcept {
packageBuilder.AddViewManager(
L"CustomUserControlViewManager", []() { return winrt::make<CustomUserControlViewManager>(); });
}
} // namespace winrt::ViewManagerSample::implementation
Here we've implemented the CreatePackage
method, which receives packageBuilder
to build contents of the package. And then we call AddViewManager
with the name of our view manager and a lambda which returns an instance of the view manager.
Now that we have the ReactPackageProvider
, it's time to register it within our ReactApplication
. We do that by simply adding the provider to the PackageProviders
property.
App.cpp
:
#include "pch.h"
#include "App.h"
#include "ReactPackageProvider.h"
#include "winrt/ViewManagerSample.h"
namespace winrt::SampleApp::implementation {
App::App() noexcept {
/* Other Init Code */
PackageProviders().Append(make<ReactPackageProvider>()); // Includes all modules in this project
PackageProviders().Append(winrt::ViewManagerSample::ReactPackageProvider());
/* Other Init Code */
}
} // namespace winrt::SampleApp::implementation
This example assumes that the ViewManagerSample::ReactPackageProvider
we created above is in a different project (assembly) than our application. However you'll notice that by default we also added a SampleApp::ReactPackageProvider
.
The SampleApp::ReactPackageProvider
is a convenience that makes sure that all native modules and view managers defined within the app project automatically get registered. So if you're creating your native modules directly within the app project, you won't actually want to define a separate ReactPackageProvider
.