react-native-xaml/USAGE.md

16 KiB

Using react-native-xaml

Requirements

react-native-xaml works best on the latest supported react-native-windows version.

To include react-native-xaml in your app, just add it to your package.json: yarn add react-native-xaml --save

Then autolinking will take care of the rest!

The stock react-native-xaml package includes projections for all XAML types (i.e. "system XAML") as well as WinUI 2.x.

Important

You must update your app to use WinUI 2.6 or later. See the instructions at https://microsoft.github.io/react-native-windows/docs/customizing-sdk-versions.

If you want to use an existing UserControl (e.g. from a library you've already written), you will want to generate a custom projection for your project. For more information on generating a custom projection, please see the Technical Guide.

Sample usage - UWP XAML

Below are usage examples of some controls and features of react-native-xaml. If you have questions about a scenario you don't see below, please file an issue!

Basic example (children, events, properties)

<StackPanel orientation="horizontal">
  <HyperlinkButton content="Click me!" onClick={(args) => {
      alert(`clicked! Native event args: ${JSON.stringify(args.nativeEvent)}`);
  }} />
  
  <Border verticalAlignment="center" background="paleturquoise" >
    <TextBlock text="this is a text block" foreground='red' textAlignment="center" />
  </Border>
  
  <TextBlock text="this is another text block" foreground='green' textAlignment="center" />
  <Button content="this is a button" onClick={() => { alert("you clicked the button!"); }} />
</StackPanel>

Text

<TextBlock foreground="black" xamlPadding={20} xamlMargin={20}>
  <Run text="hello world!"/>
  <LineBreak />
  <Italic>
    <Run text="hi there"/>
  </Italic>
  <Hyperlink navigateUri="http://bing.com">
    <Run text="Go to bing"/>
  </Hyperlink>
</TextBlock>

Menu

[isOpen, setIsOpen] = useState(false);

<TextBox text="this is a textbox with a menuFlyout" foreground="red">
  <MenuFlyout isOpen={isOpen} onClosed={() => {
    setIsOpen(false);
    }} >
    <MenuFlyoutItem text="option 1" onClick={(x) => { alert(JSON.stringify(x.nativeEvent)); setOption("option 1"); }} />
    <MenuFlyoutItem text="option 2" onClick={() => { alert("clicked 2"); setOption("option 2"); }}/>
  </MenuFlyout>
</TextBox>

<Button content={`Last selected option = ${option} ${count}`}
  onClick={(a) => { 
  setIsOpen(true); }} />

Menus can contain cascading items:

<MenuFlyout ...>
  <MenuFlyoutSubItem text='subitem 1'>
    <MenuFlyoutItem text='item 1'/>
    <MenuFlyoutItem text='item 2'/>
    <MenuFlyoutItem text='item 3'/>
  </MenuFlyoutSubItem>
  <MenuFlyoutSubItem text='subitem 2'>
    <MenuFlyoutItem text='item 4'/>
    <MenuFlyoutItem text='item 5'/>
    <MenuFlyoutItem text='item 6'/>
  </MenuFlyoutSubItem>
</MenuFlyout>

MenuBar

<MenuBar>
  <MenuBarItem title="File">
    <MenuFlyoutItem text="Help"/>
    <MenuFlyoutItem text="Settings"/>
  </MenuBarItem>
  <MenuBarItem title="View"/>
</MenuBar>

Image

  • For Image, note that you should specify its size so that RN reserves space for it, otherwise the image won't show.
  • Supports Bitmap images and SVG files URIs, as well as inline Base64-encoded data:
<Image source="https://microsoft.github.io/react-native-windows/img/homepage/cross-platform.png" 
  width={200} height={100} />
<Image
  source="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4gPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSI+PHBhdGggZD0iTTEzLjEyNSAwSDBWMTMuMTI1SDEzLjEyNVYwWiIgZmlsbD0iI0YyNTAyMiI+PC9wYXRoPjxwYXRoIGQ9Ik0yOCAwSDE0Ljg3NVYxMy4xMjVIMjhWMFoiIGZpbGw9IiM3RkJBMDAiPjwvcGF0aD48cGF0aCBkPSJNMTMuMTI1IDE0Ljg3NUgwVjI4SDEzLjEyNVYxNC44NzVaIiBmaWxsPSIjMDBBNEVGIj48L3BhdGg+PHBhdGggZD0iTTI4IDE0Ljg3NUgxNC44NzVWMjhIMjhWMTQuODc1WiIgZmlsbD0iI0ZGQjkwMCI+PC9wYXRoPjwvc3ZnPiA="
  width={80}
  height={80} />

ComboBox

<ComboBox text="this is a combobox" description="best pets" 
    onSelectionChanged={(args) => 
    { alert(`sel changed! Native event args: ${JSON.stringify(args.nativeEvent)}`); }
  } >
  <ComboBoxItem content="garfield" foreground="black" />
  <ComboBoxItem content="snoopy" foreground="black" />
</ComboBox>

AutoSuggestBox

<AutoSuggestBox placeholderText="placeholder text">
    <SymbolIcon symbol={Symbol.Find}/>
</AutoSuggestBox>

Grid

<Grid gridLayout={{columns: [60, 60, '*'], rows: [100, 100, 100]}}>
    <TextBlock text="hello" gridRow={2} gridColumn={2} />
    <TextBlock text="world" gridRow={1} gridColumn={1} />
    <TextBlock gridRow={2} gridColumn={1} text='hi there' />
</Grid>

Note that only react-native-xaml components will respect the gridRow/gridColumn properties inside a Grid, core controls will not do that.

NavigationView

const [text, setText] = React.useState('initial text');

<WinUI.NavigationView width={700} height={700}>
  <WinUI.NavigationViewItem content='Item 1' onTapped={() => setText('text #1')} priority={NavigationViewPriority.MenuItem}>
    <FontIcon glyph="&#xE790;" />
  </WinUI.NavigationViewItem>
  <WinUI.NavigationViewItem content='Item 2' onTapped={() => setText('text #2')} priority={NavigationViewPriority.FooterMenuItem}/>
  <TextBlock text={text} priority={NavigationViewPriority.Content}/>
</WinUI.NavigationView>

CommandBar with AppBarButtons and KeyboardAccelerators

<CommandBar isOpen={true} style={{ height: 56 }}>
  <AppBarButton
    priority={CommandBarPriority.PrimaryCommand}
    label="Search"
    onClick={() => { alert('search'); }}>
    <SymbolIcon symbol={Symbol.Find} />
    <KeyboardAccelerator
        virtualKey={VirtualKey.S}
        modifiers={VirtualKeyModifiers.Menu} />
  </AppBarButton>
  <AppBarButton
    priority={CommandBarPriority.PrimaryCommand}
    label="Calendar">
    <SymbolIcon symbol={Symbol.Calendar} />
  </AppBarButton>
  <AppBarButton label="Audio" priority={CommandBarPriority.SecondaryCommand}>
    <SymbolIcon symbol={Symbol.Audio} />
  </AppBarButton>
  <AppBarButton
    label="Calculator"
    priority={CommandBarPriority.SecondaryCommand}>
    <SymbolIcon symbol={Symbol.Calculator} />
  </AppBarButton>
</CommandBar>

ContentDialog

const [showState, setShowState] = useState(ContentDialogState.Hidden);

<Button
  onTapped={a => {setShowState(ContentDialogState.Popup);}}
  content="click to open a ContentDialog" />

<ContentDialog
  showState={showState}
  defaultButton={ContentDialogButton.Close}
  title="the title"
  content="this is the content"
  closeButtonText="close"
  primaryButtonText="primary"
  secondaryButtonText="secondary"
  onPrimaryButtonClick={e => {
    alert('primary');
  }}
  onSecondaryButtonClick={e => {
    alert('secondary');
  }}
  onContentDialogClosed={e => {
    setShowState(ContentDialogState.Hidden);
    alert(e.nativeEvent);
  }} />

SplitView

const [isOpen, setIsOpen] = useState(false);
// ...
<SplitView
  isPaneOpen={isOpen}
  onPaneClosed={() => {
    setIsOpen(false);
  }}
  width={800}
  height={300}
  paneBackground="red"
  panePlacement={SplitViewPanePlacement.Left}>
  <TextBlock
    text="this is in the pane"
    priority={SplitViewPriority.Pane}
    foreground="white"
  />
  <Grid
    background="green"
    priority={SplitViewPriority.Content}
    gridLayout={{rows: [200], columns: [200]}}>
    <TextBlock text="this is in the content" foreground="white" />
  </Grid>
</SplitView>


<Button
  foreground="#992222"
  onTapped={a => {
    setIsOpen(!isOpen);
  }}
  content="button"
/>

Path & vector graphics

<Path
  data="M14,2H12V1H11V2H5V1H4V2H2V14H14ZM4,3V4H5V3h6V4h1V3h1V5H3V3Zm9,10H3V6H13ZM7,10V7H4v3ZM5,8H6V9H5Z"
  width={16}
  height={16}
  fill="red"
  horizontalAlignment={HorizontalAlignment.Center}
  verticalAlignment={VerticalAlignment.Center}
/>

Lightweight styling

Using resources to style specific aspects of a control

<Button 
  content="Hello with style!" 
  resources={{ 
    ButtonForeground: "#00fff1",
    ButtonForegroundPressed: "#2090ff",
  }} 
/>

Alternatively, use styleKey to leverage system theme styles.

<Button 
  content="Hello with accent style!" 
  styleKey="AccentButtonStyle"
/>

Note: "AccentButtonStyle" used in this example is defined here. Controls will have their supported styleKeys defined in associated *_themeresources.xaml files like the one linked.

Event args

react-native-xaml supports raising events and sending event args to the registered JavaScript event handlers. The native XAML event args object is projected to JavaScript through the nativeEvent method:

  <HyperlinkButton content="Click me!" 
    onClick={(args) => {
      alert(JSON.stringify(args.nativeEvent));
    }} />

The content of the native event args object depends on whether the app is using Direct debugging or Web debugging.

  • In web debugging mode, only certain properties on the sender and args are exposed. There is some special casing for a small number of events so that the "important properties" for the event can be projected to JavaScript. These properties must be of simple types (number, string, boolean, arrays of simple types, or objects with members of simple types).

  • In direct debugging mode, react-native-xaml uses codegen to infer how to marshal WinRT objects onto JavaScript by using JSI. This is a much richer and generic functionality. With this approach, you have access to the full set of properties on the event args and sender.

Sample JSON from an event args for a button being tapped
{
  "sender": {
    "flowDirection": 0,
    "className": "Windows.UI.Xaml.Controls.Button",
    "allowFocusOnInteraction": true,
    "isFocusEngagementEnabled": false,
    "focusVisualMargin": {
      "left": -3,
      "top": -3,
      "right": -3,
      "bottom": -3
    },
    "allowFocusWhenDisabled": false,
    "background": "Black",
    "isTextScaleFactorEnabled": true,
    "focusVisualSecondaryBrush": "White",
    "backgroundSizing": 1,
    "borderBrush": "White",
    "horizontalAlignment": 0,
    "borderThickness": {
      "left": 1,
      "top": 1,
      "right": 1,
      "bottom": 1
    },
    "characterSpacing": 0,
    "clickMode": 0,
    "defaultStyleKey": {
      "kind": 1,
      "name": "Windows.UI.Xaml.Controls.Button"
    },
    "commandParameter": null,
    "content": "button",
    "focusVisualPrimaryThickness": {
      "left": 2,
      "top": 2,
      "right": 2,
      "bottom": 2
    },
    "dataContext": null,
    "height": 32,
    "defaultStyleResourceUri": null,
    "elementSoundMode": 0,
    "tabIndex": 2147483647,
    "focusVisualPrimaryBrush": "Black",
    "focusVisualSecondaryThickness": {
      "left": 1,
      "top": 1,
      "right": 1,
      "bottom": 1
    },
    "fontSize": 14,
    "margin": {
      "left": 0,
      "top": 0,
      "right": 0,
      "bottom": 0
    },
    "fontStretch": 5,
    "isTabStop": true,
    "fontStyle": 0,
    "foreground": "Dark red",
    "horizontalContentAlignment": 1,
    "tabNavigation": 0,
    "isEnabled": true,
    "isFocusEngaged": false,
    "language": "en-US",
    "maxHeight": null,
    "maxWidth": null,
    "tag": 13,
    "minHeight": 0,
    "minWidth": 0,
    "name": "<reacttag>: 13",
    "padding": {
      "left": 8,
      "top": 5,
      "right": 8,
      "bottom": 6
    },
    "requestedTheme": 0,
    "requiresPointer": 0,
    "verticalAlignment": 1,
    "useSystemFocusVisuals": true,
    "verticalContentAlignment": 1,
    "width": 1200
  },
  "args": {
    "className": "Windows.UI.Xaml.Input.TappedRoutedEventArgs",
    "handled": false,
    "pointerDeviceType": 2,
    "originalSource": {
      "flowDirection": 0,
      "className": "Windows.UI.Xaml.Controls.TextBlock",
      "allowFocusOnInteraction": true,
      "focusVisualMargin": {
        "left": 0,
        "top": 0,
        "right": 0,
        "bottom": 0
      },
      "allowFocusWhenDisabled": false,
      "characterSpacing": 0,
      "maxLines": 0,
      "isTextSelectionEnabled": false,
      "focusVisualPrimaryThickness": {
        "left": 2,
        "top": 2,
        "right": 2,
        "bottom": 2
      },
      "dataContext": "button",
      "focusVisualPrimaryBrush": "Black",
      "isTextScaleFactorEnabled": true,
      "focusVisualSecondaryBrush": "White",
      "focusVisualSecondaryThickness": {
        "left": 1,
        "top": 1,
        "right": 1,
        "bottom": 1
      },
      "fontSize": 14,
      "margin": {
        "left": 0,
        "top": 0,
        "right": 0,
        "bottom": 0
      },
      "fontStretch": 5,
      "lineHeight": 0,
      "fontStyle": 0,
      "foreground": "Black",
      "height": null,
      "horizontalAlignment": 0,
      "horizontalTextAlignment": 1,
      "isColorFontEnabled": true,
      "language": "en-US",
      "lineStackingStrategy": 0,
      "maxHeight": null,
      "maxWidth": null,
      "tag": null,
      "minHeight": 0,
      "minWidth": 0,
      "name": "",
      "opticalMarginAlignment": 0,
      "padding": {
        "left": 0,
        "top": 0,
        "right": 0,
        "bottom": 0
      },
      "requestedTheme": 0,
      "selectionHighlightColor": "Blue",
      "text": "button",
      "textAlignment": 1,
      "textReadingOrder": 1,
      "textLineBounds": 0,
      "textDecorations": 0,
      "textTrimming": 0,
      "textWrapping": 1,
      "verticalAlignment": 0,
      "width": null
    }
  }
}

In order to enable Direct debugging for your app, make sure that your App.cpp/App.cs has disabled web debugging:

C++
  InstanceSettings().UseWebDebugger(false);
C#
  InstanceSettings.UseWebDebugger = false;

Calling methods on XAML Event Args

By default, only XAML properties of event arg objects are exposed back. Some event args support calling methods on them. See eventArgMethods in Windows.UI.Xaml.json.

Example:

/// assume there is a MenuFlyout _menuRef defined like this:

const menu = useRef<MenuFlyoutRef>(null);

/// then:
<TextBlock
  text="Hello"
  onContextRequested={e => {
    const tag = findNodeHandle(_tbRef.current);
    const { point, returnValue } = e.nativeEvent.args.TryGetPosition(tag);
    MenuFlyout.ShowAt(_menuRef, {point: point});
  }}
/>

Calling methods on XAML objects ("commands")

Some types support custom commands to expose some functionality of the underlying platform. For example, this allows calling the programatically showing a flyout menu:

  const _tbRef = React.useRef<TextBlockRef>(null);
  const _menuRef = useRef<MenuFlyoutRef>(null);

  const [x, setX] = React.useState(100);
  // ...
  return
<TextBox
  text={x}
  onBeforeTextChanging={e => {
    setX(e.nativeEvent.args.newText);
}}
/>
<TextBlock
  text="Hello"
  onTapped={e => {
    MenuFlyout.ShowAt(_menuRef, {point: {x: x, y: 42}});
  }}
  ref={t => {
    _tbRef.current = t;
  }}>
    <MenuFlyout
      ref={m => {
        _menuRef.current = m;
      }}>
      <MenuFlyoutItem text="menu option" />
    </MenuFlyout>
</TextBlock>

Sample usage - WinUI controls

WinUI controls are available in the WinUI namespace.

InfoBar

const [visible, setVisible] = useState(Visibility.Visible);
// ...
<WinUI.InfoBar
  message="the message"
  title="the title"
  isOpen={true}
  visibility={visible}
  onClosed={() => {
    setVisible(Visibility.Collapsed);
  }}
  severity={WinUIEnums.InfoBarSeverity.Success}
/>