react-native-xaml/USAGE.md

15 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.6.

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 textblock" foreground='red' textAlignment="center" />
  </Border>
  
  <TextBlock text="this is another textblock" 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 bois" 
    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

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

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.
<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<TextBlock>(null);
  const _menuRef = useRef<MenuFlyout>(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}
/>