UI/HDTWizardShell.xaml
|
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Hephaestus Deployment Toolkit" Height="700" Width="900" WindowStartupLocation="CenterScreen" ResizeMode="NoResize" WindowStyle="None" Background="#FF1E1E1E"> <!-- MDT'S LITETOUCH WIZARD, REBUILT - the shape an MDT admin already knows: a coloured rail down the left listing the pages, the current page's content to the right of it, and Back / Next / Cancel at the bottom. PSDWizard_Template_Classic_en-US.xaml is the closest prior art and this follows its layout deliberately (NOTICE.md). WHERE IT DEVIATES FROM PSD, AND WHY BOTH ARE FORCED: NO x:Class. PSD's template declares PSDWizard.MainWindow and relies on code-behind. There is no compiler in WinPE; New-HDTWizardHost attaches every handler through FindName instead, and tests/contract/WinPeUiStack.Contract.Tests.ps1 refuses a code-behind class outright. NO MERGED ResourceDictionary. PSD's template merges six of them by Source=. That is six more files that must reach the RAM disk intact, and a page that half-loads in WinPE is a XamlParseException where a wizard should be. The host merges HDTTheme.xaml at runtime instead, so this file names no other file at all. THE RAIL IS A LIST, NOT A TabControl. MDT's panes are not tabs a technician may click between - the order is the deployment's order, and a page becomes reachable by being reached. The rail SHOWS progress; the buttons MAKE it. HDTPageList is therefore display-only, and the host drives HDTPageHost. EVERY PAGE IS INDIVIDUALLY SKIPPABLE (DESIGN 11.2). A page whose values are all supplied never appears, and never appears in the rail either - the rail lists what this deployment will actually ask, not the catalogue of what a deployment could ask. --> <!-- THE ROOT PAINTS ITS OWN GROUND. Window.Background alone is not enough: anything that renders this tree without the Window around it - a preview, a screenshot harness, a page hosted elsewhere - gets whatever is behind it, and white text on white ground is a wizard nobody can read. It cost exactly one screenshot to learn. --> <Grid Background="#FF1E1E1E"> <Grid.ColumnDefinitions> <ColumnDefinition Width="230" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <!-- THREE ROWS: THE PAGE, WHAT IS WRONG WITH IT, THEN THE BUTTONS. The middle one is new and it is a row rather than a column for a measured reason - see the message band below. --> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="76" /> </Grid.RowDefinitions> <!-- ============ the rail ============ --> <!-- DARKER THAN THE ACCENT, not the accent itself: the rail is a backdrop that must never compete with the selected row painted on top of it in that accent. --> <Border Grid.Row="0" Grid.RowSpan="3" Grid.Column="0" Background="#FF083B5C" x:Name="HDTDragBanner"> <DockPanel LastChildFill="True"> <!-- BOTH LINES WRAP, AND THE HEADING IS WHY. It read 'Hephaestus' on every machine ever built, which fits this rail with room to spare - so nothing revealed that the TextBlock had no wrapping at all until HDTBrandingName let a site put its own name here and 'Contoso Field Services' came out as 'Contoso Field S', clipped mid-word with no ellipsis to say it had been. A banner that silently truncates the one thing on it that identifies the deployment is worse than no banner. HDTShellBuild below has wrapped since it was written. --> <StackPanel DockPanel.Dock="Top" Margin="24,28,24,24"> <TextBlock x:Name="HDTShellTitle" TextWrapping="Wrap" Foreground="White" FontSize="26" FontWeight="SemiBold" /> <TextBlock x:Name="HDTShellSubtitle" TextWrapping="Wrap" Foreground="#FFA9CDE6" FontSize="15" Margin="0,2,0,0" /> </StackPanel> <!-- THE VERSION AND THE BUILD, at the bottom of the rail where MDT puts them. On a machine that has just failed, the first question is always WHICH IMAGE THIS IS - and the answer has to be on screen, because the log is on a share the machine may not have reached. --> <TextBlock DockPanel.Dock="Bottom" x:Name="HDTShellBuild" Text="" Foreground="#FF7FA8C4" FontSize="13" Margin="24,0,24,20" TextWrapping="Wrap" /> <!-- EACH ROW STATES WHAT IT IS, AND THIS DECIDES WHAT THAT LOOKS LIKE. Step-HDTWizardPage hands over Title and State - Done, Current or Pending - and nothing else. It used to be handed a Fill and an Ink brush, which put the look in a PowerShell command: a second place the palette is defined, and the first one to drift from HDTTheme.xaml. --> <!-- IsTabStop="False" BECAUSE THIS IS A PICTURE, NOT A CONTROL. Control.IsTabStop defaults to TRUE and the rail inherited it, so the FIRST Tab on every page landed here - on an ItemsControl of Borders and TextBlocks with nothing to click. Measured with an STA probe walking MoveFocus: Tab 1 the rail, Tab 2 the page host, Tab 3 the first real field. --> <ItemsControl x:Name="HDTPageList" Margin="0,8,0,0" IsTabStop="False" Background="Transparent" BorderThickness="0"> <ItemsControl.ItemTemplate> <DataTemplate> <Border Padding="24,10,16,10"> <Border.Style> <Style TargetType="Border"> <Setter Property="Background" Value="Transparent" /> <Style.Triggers> <!-- THE ACCENT, and only on the row being asked. --> <DataTrigger Binding="{Binding State}" Value="Current"> <Setter Property="Background" Value="#FF0E639C" /> </DataTrigger> </Style.Triggers> </Style> </Border.Style> <TextBlock Text="{Binding Title}" FontSize="16" TextWrapping="Wrap"> <TextBlock.Style> <Style TargetType="TextBlock"> <!-- PENDING IS THE DIMMEST, and it is the default because most of the rail is ahead of the technician. --> <Setter Property="Foreground" Value="#FF7FA8C4" /> <Style.Triggers> <DataTrigger Binding="{Binding State}" Value="Done"> <Setter Property="Foreground" Value="#FFA9CDE6" /> </DataTrigger> <DataTrigger Binding="{Binding State}" Value="Current"> <Setter Property="Foreground" Value="White" /> </DataTrigger> </Style.Triggers> </Style> </TextBlock.Style> </TextBlock> </Border> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </DockPanel> </Border> <!-- ============ the page ============ --> <Grid Grid.Row="0" Grid.Column="1" Margin="36,32,36,0"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <TextBlock Grid.Row="0" x:Name="HDTPageHeading" Text="" Foreground="White" FontSize="26" FontWeight="SemiBold" /> <TextBlock Grid.Row="1" x:Name="HDTPageSubheading" Text="" Foreground="#FFCCCCCC" FontSize="16" Margin="0,6,0,0" TextWrapping="Wrap" /> <!-- WHERE THE PAGE ITSELF GOES. The host parses the page's own XAML and drops it in here, which is what lets DESIGN 11.2's eight pages live on the SHARE under Scripts\UI and be redesigned without rebuilding a boot image. IT SCROLLS, AND THE PAGE THAT PROVED IT WAS COMPUTER DETAILS. A name, a domain-or-workgroup choice with five boxes under it, and the panel explaining what a computer name may hold do not fit in this window - and the half that fell off the bottom was the workgroup, which is the option most machines take. A page host that clips is a page host that hides whichever control was added last. THE SHELL SCROLLS, NOT THE PAGES. Every page would otherwise have to carry its own ScrollViewer and remember to, and the one that forgot would be the one that grew. Pages live on the SHARE and are edited by administrators who cannot be expected to know the window is 700px tall. HORIZONTAL IS DISABLED ON PURPOSE. A page wide enough to scroll sideways is a layout fault to fix rather than a thing to give a technician a second scrollbar for. --> <ScrollViewer Grid.Row="2" Margin="0,24,0,0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Padding="0,0,12,0"> <!-- AND THE SECOND DEAD STOP. The container is not the thing being filled in; what is loaded INTO it is, and that is where Tab should arrive. --> <ContentControl x:Name="HDTPageHost" IsTabStop="False" /></ScrollViewer> </Grid> <!-- ============ what is wrong with the page ============ --> <!-- A ROW OF ITS OWN, AND THE TWO FIXES BEFORE THIS ONE ARE WHY. IT STARTED IN THE BUTTONS' CELL, pinned left with MaxWidth="300" so it could not run under them - and a refusal long enough to be useful was CLIPPED SIDEWAYS. The cap came off and the message was given the `*` column beside the buttons instead. That did not fix the clip; it TURNED IT NINETY DEGREES. Measured on the shipped window: Open CMD 120 + Back 110 + Cancel 110 + Deploy 130 and their margins leave the message column 64.6px wide, so 'the two passwords do not match. Type the same password in both boxes.' wraps to 167.6px inside a 76px row - a narrow orange ribbon with its first and last lines cut off. SO NEITHER DIMENSION IS CAPPED NOW. The message competes with nothing: it gets the whole strip the buttons stand on - 900 less the 230 rail less its own margins, 598px - and its row is Auto, so a sentence longer than the reserve makes the band taller instead of losing a line. That is the difference from MaxWidth="300": that was a cap, this is a row. MinHeight IS THE ROOM ALWAYS THERE, AND IT IS WHY NOTHING JUMPS. 58px holds two 18.6px lines with the margins below, which is what every refusal a shipped page can produce needs - so the band is the same height empty as it is full, the buttons never move, and the page above never resizes as a technician types. A layout that jumps while you fill it in is worse than one that clips. IT IS PAINTED IN THE FOOTER'S OWN GREY so the strip and the buttons read as one bar rather than as a slab that appeared under the page. tests/unit/WizardShellMessage.Tests.ps1 measures all of this against the SET - every refusal harvested from Show-HDTWizardShell's rule table and the validators it names - so the next rule with a longer sentence fails there rather than on a bench. --> <Border Grid.Row="1" Grid.Column="1" Background="#FF252526" MinHeight="58"> <!-- A WARNING IS NOT A REFUSAL, AND MUST NOT LOOK LIKE ONE. HDT_01 is a legal computer name that DNS cannot carry: the wizard lets it through and says so, and painting that in the same red as a refusal would make a technician go looking for what they had done wrong. THE SEVERITY ARRIVES IN Tag, and the colour is decided here. Test-HDTComputerName returns 'None', 'Warning' or 'Error'; the host sets Tag and paints nothing, which is the same split as the rail - the engine states what a thing IS and the markup decides what that looks like. NO LOCAL Foreground. A local value beats a Style trigger, so the attribute that used to be here would have won over both triggers and made the warning red anyway. TOP, NOT CENTRE. A two-line message centred in a band sized for two lines is the same thing; a three-line one centred in a band that grew for it is a message drifting away from the control it is about. It grows downwards, towards the buttons it is explaining. --> <TextBlock x:Name="HDTMessageText" Text="" VerticalAlignment="Top" FontSize="14" TextWrapping="Wrap" Margin="36,12,36,8"> <TextBlock.Style> <Style TargetType="TextBlock"> <Setter Property="Foreground" Value="#FFF48771" /> <Style.Triggers> <DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="Warning"> <Setter Property="Foreground" Value="#FFE2C08D" /> </DataTrigger> </Style.Triggers> </Style> </TextBlock.Style> </TextBlock> </Border> <!-- ============ the buttons ============ --> <Border Grid.Row="2" Grid.Column="1" Background="#FF252526"> <Grid Margin="36,0,36,0"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center"> <!-- OPEN CMD IS NOT A WAY OUT, it is the diagnostic door - and it stays on every page because the page a technician is stuck on is the page they need a prompt from. HDTOpenCmdButton, AND THE NAME IS NOT A DETAIL. This button shipped as HDTCommandPromptButton, which is what the ANSWER is called - but New-HDTWizardHost wires the BUTTON, and it wires HDTOpenCmdButton, the name HDTWelcome.xaml uses. Both files read correctly on their own and every assertion passed. The button was dead: it opened no prompt and did not even close the window. WinPeUiStack.Contract.Tests.ps1 now refuses a named button no engine code mentions. --> <Button x:Name="HDTOpenCmdButton" Width="120" Height="38" Background="#FF3A3A3C" Margin="0,0,16,0" /> <Button x:Name="HDTBackButton" Width="110" Height="38" Background="#FF3A3A3C" Margin="0,0,10,0" /> <Button x:Name="HDTCancelButton" Width="110" Height="38" Background="#FF3A3A3C" Margin="0,0,10,0" /> <!-- IsDefault IS ENTER, AND THE WIZARD HAD NO KEYBOARD WAY FORWARD WITHOUT IT. A technician who typed a computer name and pressed Enter got nothing and had to reach for the mouse - on a bench, on a machine with a trackpad nobody has configured yet, on every page. IT CANNOT SKIP A BAD PAGE. WPF fires the default button only when it is enabled, and the validator already drives IsEnabled - so Enter on a page whose answer is refused does exactly what clicking Next does: nothing. NOT IsCancel ON Cancel, deliberately. Escape would then discard everything typed so far, from one stray key press, with no confirmation - and the wizard is the only thing standing between that key and a disk being repartitioned. --> <Button x:Name="HDTNextButton" IsDefault="True" Width="130" Height="38" Background="#FF0E639C" /> </StackPanel> </Grid> </Border> </Grid> </Window> |