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

-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();
+    }
+  }
}
+}
-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

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.

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.