Skip to content
Shiny.NET

Office Shell

The application window around the Office editors — the chrome that makes a DocumentEditor, SpreadsheetView or SlideEditor look like Word, Excel or PowerPoint rather than a canvas with a toolbar. It is modelled on the Microsoft 365 web apps: an accent title bar with quick access and a command search, the Ribbon with Comments / mode / Share at its right end, rulers, a navigation pane, a comments pane, a status bar with view modes and zoom, and the File backstage.

  • NuGet downloads for Shiny.Maui.Controls.Office
  • NuGet downloads for Shiny.Blazor.Controls.Office
Frameworks
.NET MAUI
Blazor

Every part can be used on its own, and OfficeShell arranges them. The shell reads and writes no files: every task — open, save, save as PDF, print, pick a template — is an event carrying what was chosen, so the host decides what it means where it runs.

Namespaces: Shiny.Maui.Controls.Office / Shiny.Blazor.Controls.Office for the parts, and Shiny.Controls.Office.Shell for the shared models (OfficeApp, OfficeCommandIndex, OfficeStatusItem, OfficeZoomModel, OfficeRulerModel, OfficeTemplate…). Icons are OfficeShellIcon in Shiny.Controls.Office.Icons.

Part What it is
OfficeShell The container: slots for title bar, ribbon, ruler, vertical ruler, left pane, content, right pane, status bar and backstage. Owns focus mode, the backstage overlay and the responsive layout
OfficeTitleBar AutoSave switch, Save / Undo / Redo plus extra quick access, the document name with a rename dropdown, the save status (“Saved locally” / “Saving…” / “Unsaved changes”), the “Search for tools, help, and more” command search, Help and the account avatar
OfficeRibbonActions Comments toggle, Editing / Reviewing / Viewing dropdown and Share — for the ribbon’s header-end slot
OfficeBackstage The File page: an accent rail with Back, Home, New, Open, Info, Save, Save As, Print, Export, History (optional) and Options
OfficeStatusBar Editor-fed segments on the left; Focus, three view-mode buttons, and zoom − / slider / + / percentage on the right
OfficeZoomDialog / OfficeDialog Word’s Zoom dialog (200 / 100 / 75 / page width / text width / whole page / custom), and the plain OK/Cancel dialog it is built on. On Blazor OfficeDialog wraps the core ModalView
OfficeRuler Word’s ruler — inches or cm, margin shading, draggable first-line / hanging / left / right indents, tab stops and the tab-kind selector; horizontal or vertical
OfficeStyleGallery The “AaBbCcDd” Styles gallery — a ribbon item
OfficeNavigationPane Search box, Headings tree, optional Pages tab, Results
OfficeSidePane A titled pane with a close button, for Comments or anything else
OfficeShellIconView (MAUI) / OfficeShellGlyph + OfficeShellIcons.Svg() (Blazor) The shell’s icon set

OfficeApp.Word / Excel / PowerPoint / OneNote sets:

Word Excel PowerPoint
Accent #185ABD #107C41 #C43E1C
Letter (not drawn by the shell — for hosts that show their own mark) W X P
Default name Document1 Book1 Presentation1
Status bar view modes Read / Print / Web Normal / Page Layout / Page Break Preview Normal / Slide Sorter / Reading View

It also picks the Save As / Export formats. Set it on the shell; the parts inside inherit it.

<OfficeShell App="OfficeApp.Word"
@bind-IsBackstageOpen="backstage"
@bind-IsRightPaneOpen="comments"
ShellLayoutChanged="l => simplified = l.SimplifiedRibbon"
style="height:100vh">
<TitleBar>
<OfficeTitleBar @bind-DocumentName="name" SaveState="saveState" @bind-AutoSave="autoSave"
CanUndo="canUndo" CanRedo="canRedo" CommandIndex="commands"
SaveRequested="SaveAsync" UndoRequested="Undo" RedoRequested="Redo"
UserName="Allan Ritchie" SearchSubmitted="FindInDocument" />
</TitleBar>
<Ribbon>
<Ribbon @ref="ribbon" ApplicationButtonText="File" ApplicationButtonClicked="() => backstage = true"
DisplayMode="@(simplified ? RibbonDisplayMode.Simplified : RibbonDisplayMode.Expanded)">
<HeaderEnd><OfficeRibbonActions @bind-EditMode="mode" ShareClicked="Share" /></HeaderEnd>
<ChildContent>
<RibbonTab Title="Home">
<RibbonGroup Title="Styles">
<OfficeStyleGallery @bind-SelectedStyleId="styleId" StyleSelected="ApplyStyle" />
</RibbonGroup>
</RibbonTab>
</ChildContent>
</Ribbon>
</Ribbon>
<Ruler><OfficeRuler PageWidth="612" @bind-Indents="indents" @bind-TabStops="tabs" Zoom="zoom" PageOffset="pageLeft" /></Ruler>
<LeftPane><OfficeNavigationPane Headings="headings" HeadingSelected="GoTo" SearchRequested="Search" SearchResults="hits" /></LeftPane>
<ChildContent><DocumentEditor @ref="editor" Document="document" Zoom="zoom" /></ChildContent>
<RightPane><OfficeSidePane Title="Comments">…</OfficeSidePane></RightPane>
<StatusBar><OfficeStatusBar Items="status" @bind-Zoom="zoom" @bind-SelectedViewMode="view" /></StatusBar>
<Backstage>
<OfficeBackstage Templates="templates" RecentFiles="recent" DocumentInfo="info" Options="options"
TemplateSelected="NewFromAsync" RecentFileSelected="OpenAsync" OpenRequested="BrowseAsync"
SaveRequested="SaveAsync" SaveAsRequested="SaveAsAsync" ExportRequested="ExportAsync"
PrintRequested="PrintAsync">
<PrintPreview><img src="@previewUrl" /></PrintPreview>
</OfficeBackstage>
</Backstage>
</OfficeShell>
@code {
readonly OfficeCommandIndex commands = new();
Ribbon? ribbon;
IDisposable? sync;
protected override void OnAfterRender(bool first)
{
if (first && ribbon is not null)
sync = commands.SyncRibbon(ribbon); // the search finds every rendered ribbon command
}
}

The shell cascades itself, so the parts pick up its app, accent and compact layout. The status bar’s Focus button, the backstage’s Back and a side pane’s close drive the shell directly, and OfficeRibbonActions’ Comments button opens and closes the right pane. Escape leaves the backstage and then focus mode. The width comes from a ResizeObserver in officeShell.js; without the script the shell stays at the desktop layout.

<office:OfficeShell App="Word" IsBackstageOpen="{Binding Backstage}" IsRightPaneOpen="{Binding Comments}">
<office:OfficeShell.TitleBar>
<office:OfficeTitleBar DocumentName="{Binding Name}" SaveState="{Binding SaveState}"
CanUndo="{Binding CanUndo}" CommandIndex="{Binding Commands}"
SaveCommand="{Binding Save}" UndoCommand="{Binding Undo}" RedoCommand="{Binding Redo}" />
</office:OfficeShell.TitleBar>
<office:OfficeShell.Ribbon>
<shiny:Ribbon x:Name="Ribbon"> … </shiny:Ribbon>
</office:OfficeShell.Ribbon>
<office:OfficeShell.StatusBar>
<office:OfficeStatusBar x:Name="Status" Zoom="{Binding Zoom}" />
</office:OfficeShell.StatusBar>
<office:OfficeShell.Backstage>
<office:OfficeBackstage Templates="{Binding Templates}" RecentFiles="{Binding Recent}"
SaveAsRequested="OnSaveAs" ExportRequested="OnExport" />
</office:OfficeShell.Backstage>
<office:DocumentEditor x:Name="Editor" /> <!-- ShellContent is the content property -->
</office:OfficeShell>
commands.AddRibbon(Ribbon); // MAUI sees every tab, opened or not
Status.Items.Add(pageItem = new OfficeStatusItem("page", "Page 1 of 1") { IsClickable = true });
Status.Items.Add(wordsItem = new OfficeStatusItem("words", "0 words"));
// later, as the caret moves:
pageItem.Text = OfficeStatusText.Page(page, pages);
wordsItem.Text = OfficeStatusText.Words(count);

What differs on MAUI:

  • The layout is ShellLayout / ShellLayoutChanged, as on Blazor.
  • A Ribbon in the Ribbon slot is wired automatically — its File button opens the backstage (and gets “File” as its text if it had none), and below the compact width it is switched to Simplified and back.
  • An OfficeStatusBar’s Focus button toggles focus mode, and the zoom dialog is hosted by the shell.
  • Everything is built up front and shown or hidden, so the macOS AppKit head renders it. Lists that change after layout (backstage templates and recents, headings, status segments) may not repaint on AppKit until a resize.

Public seams for tests and keyboard shortcuts: OfficeTitleBar.Search / SubmitSearchAsync / Rename, OfficeStatusBar.ZoomIn / ZoomOut / SetZoomFromSlider / OpenZoomDialog, OfficeRuler.BeginDrag / DragTo / EndDrag / TapAt, OfficeBackstage.SelectPage / ChooseTemplate / ChooseSaveAs / ChooseExport / Save / Close, and OfficeShell.ToggleFocusMode / OpenBackstage.

OfficeCommandIndex is the list the title bar searches. Fill it from the ribbon — AddRibbon(ribbon), or SyncRibbon(ribbon) to keep it current — and add anything the ribbon does not carry:

commands.Add("Go To", () => ShowGoTo(), "Home › Editing", "Ctrl+G", "jump", "page");
commands.Add(new OfficeCommand("Word Count", ShowWordCount) { Category = "Review", CanExecute = () => document is not null });

Ranking goes: whole label, then label prefix, then a word inside the label (“font” finds “Grow Font”), then keywords and category, then a subsequence (“fcol” finds “Font Colour”); ties go to the shorter label. Enter runs the highlighted match. A query that matches nothing raises SearchSubmitted, so the host can search the document instead.

MAUI’s ribbon list covers every tab. Blazor’s covers the tabs that have rendered, because a tab’s items only exist while it is showing — see the Ribbon’s command list.

Segments are OfficeStatusItems, and they are observable: set Text, IsVisible, IsClickable. The words come from OfficeStatusText:

Call Reads
Page(1, 3) Page 1 of 3
Words(197) 197 words
Slide(3, 12) Slide 3 of 12
Aggregates(values, count) Excel’s “Average: 4 Count: 3 Sum: 12” — null for a single cell

Zoom is a factor (1 = 100%) — the same unit every editor’s Zoom takes, so bind them together. OfficeZoomModel holds the range (10–500%), the snap point (100%) and the step (10%). The slider is piecewise: its left half is 10–100% and its right half 100–500%, so 100% sits in the middle. Give the status bar PageWidth, PageHeight, TextWidth, ViewportWidth and ViewportHeight (same units, e.g. pixels at 100%) and the zoom dialog’s Page width / Text width / Whole page presets light up.

The ruler is pure — give it the geometry and it reports what the user dragged:

Property
PageWidth, LeftMargin, RightMargin Points
Indents OfficeIndents(Left, FirstLine, Right) — Word’s model: FirstLine is relative to Left, negative for a hanging indent
TabStops, Unit Tab stops, and inches or cm
Zoom, PixelsPerPoint The editor’s zoom, and 96/72
PageOffset Where the page’s left edge is, in pixels from the ruler’s left edge — the editor’s scroll and centring

Dragging the top triangle moves the first line; the bottom triangle moves the wrapped lines and keeps the first line where it was; the box moves both. A click on the text span adds a tab of the selector’s kind; dragging a tab off the ruler removes it. Everything snaps to 1/16” (or 0.25 cm), and the text column never drops below half an inch. Blazor reports on release (LiveUpdate="true" for every step); MAUI reports live. Orientation="Vertical" draws the page’s height with top and bottom margins and no indents.

Member
Templates OfficeTemplate: Id, Name, Description, Thumbnail URL/path, Category, IsBlank, Open stream factory, Tag. The blank one is added when missing
RecentFiles OfficeRecentFile: Name, Location, LastOpened, IsPinned, App, Tag — pinned first, then newest
DocumentInfo OfficeDocumentInfo: title, author, location, created, modified, size and app Statistics like Words / Pages / Slides
Save As / Export Default to the app’s formats (OfficeFileFormats: docx / xlsx / pptx, pdf, txt, html, csv, png, jpg) and raise the chosen OfficeFileFormat (Id, Extension, MimeType, FileNameFor(name))
Options Edits an OfficeShellOptions (user name, initials, theme System / Light / Dark, AutoSave) in place and raises OptionsChanged — the shell applies none of it; theming and saving are the host’s

Below 600px (OfficeShellLayout.CompactWidth) the title bar’s search collapses to an icon and the save status hides, rulers and side panes step away (their open state is kept), the ribbon should go Simplified, and the status bar drops Focus, the view modes and the slider. Below 900px the zoom slider goes on its own.

IsFocusMode hides the title bar, ribbon, rulers, panes and status bar; a floating Exit Focus button (and Escape on Blazor) brings them back. Read mode is the same switch — pair it with the editor’s own read-only or reflow setting.

.NET MAUI (iPad) Blazor WebAssembly
The Word backstage on iPad: Home with Blank, Report and Letter template thumbnails and the recent list The Word backstage on Blazor: Home with Blank, Report and Letter template thumbnails and the host-supplied recent list
The PowerPoint window on iPad: accent title bar, ribbon, slide thumbnails and status bar with view modes and zoom The title bar command search open on Blazor, listing matching commands with their ribbon path and shortcut