UI/Console/HDTConsole.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="900" Width="1800" MinHeight="520" MinWidth="900" WindowStartupLocation="Manual" Background="{DynamicResource HDTWindowBrush}"> <!-- C1 of the WPF-first direction: the admin console, showing what is on a deployment share. NO x:Class AND NO CODE-BEHIND, exactly as HDTWizard.xaml. The console runs on a desktop where a compiler exists, so this one is a choice rather than a constraint - and the choice is that a page written this way can move into the WinPE wizard later without being rewritten, where XamlReader::Load is the only option there is. Handlers are attached in PowerShell, by name, after the tree is loaded. WindowStartupLocation IS Manual BECAUSE THE POSITION IS DECIDED, NOT GUESSED. This window opens at the top-left of the work area, filling it, which is where an administrator's other management tooling sits. Manual is the only value under which WPF honours the Left and Top the host assigns - CenterScreen recentres after the assignment and throws them away, and the two would happen to agree on this desk only because a work-area-sized window centres back onto the corner it started at. THE CORNER IS THE WORK AREA'S, NOT A LITERAL 0,0. A taskbar docked at the top or the left moves the origin, and a window pinned to zero would open underneath it. Resolve-HDTConsoleWindowPosition measures that; the four numbers above are only what a window loaded on its own falls back to. NAMED CONTROLS ARE THE CONTRACT between this file and New-HDTConsoleHost: HDTShareText, HDTDeployRootText, HDTRootText, HDTConsoleTree, HDTDetailText, HDTCommandText and HDTCloseButton are found with FindName, and tests/unit/ConsoleWindow.Tests.ps1 asserts every one of those names is present, so renaming one here fails on a developer machine rather than in front of an administrator. EVERY COLOUR IS A DynamicResource, AND THE LIGHT PALETTE IS DECLARED BELOW. The values here are the defaults, so this file renders correctly on its own; New-HDTConsoleHost replaces the same keys with whatever Get-HDTConsoleTheme returned, which is how one window file serves both the light and dark palettes without a second copy to keep in step. The keys are asserted against that command, so a colour added to one and not the other fails on a developer machine. THE TREE EXPANDS AND COLLAPSES, AND IT IS STILL THE HOST THAT DECIDES NOTHING. A HierarchicalDataTemplate binds Children, so WPF builds the nesting itself from rows Get-HDTConsoleTreeNode already linked together; the host assigns ItemsSource and attaches one handler. Icon, Text and IsExpanded are read off the row, which means what a task sequence looks like and which branches start open are decisions made in a command and asserted in tests/unit/ConsoleTreeNode.Tests.ps1 - not in this file, and not in the adapter. THE DETAIL PANE IS MONOSPACED AND THE TREE IS NOT. The pane shows 64-character SHA-256 hashes, which are read by comparing columns; the tree shows names, which are read as words. THE TREE NEVER SCROLLS SIDEWAYS. WPF brings a newly selected TreeViewItem into view horizontally as well as vertically, so clicking a row with a long name drags the whole tree left and takes the icons and the indentation off the edge of the panel with it - the tree stops looking like a tree, and the thing an administrator just clicked is the only row still readable. Horizontal scrolling is therefore off and long names end in an ellipsis. Nothing is lost: the full name is the second line of the detail pane, which is where Workbench puts it too. THE ICONS ARE CHARACTERS. A .png is another file to ship, find at runtime and get right in two themes. These come out of Windows' own fonts, scale with the row, and survive being copied out as text. THE DEFAULT SIZE IS SIZED AGAINST THE BOOT IMAGE, which is the longest pane in the console - seventeen fields, two of them 64-character hashes. Measured rather than guessed: at 720 the pane was 146 units short, and it wants 866 to show everything at once, so this opens at 900 with room to spare, inside the 1104-unit work area of the machine it was measured on. 1800 wide is what puts a full SHA-256 on ONE line beside its caption. The default must never be a shape that wraps a hash or hides one behind a scrollbar. THESE ARE THE FIRST-RUN NUMBERS ONLY. Get-HDTConsoleSetting reads the size the console was last left at and New-HDTConsoleHost applies it, so an administrator sizes this window once. The values here are what a machine with no remembered size opens at, and Hephaestus.psm1 carries the same four numbers for the reader and the writer to agree on - a test asserts the two files match. --> <Window.Resources> <SolidColorBrush x:Key="HDTWindowBrush" Color="#FFF3F3F3" /> <SolidColorBrush x:Key="HDTBannerBrush" Color="#FF0E639C" /> <SolidColorBrush x:Key="HDTBannerTextBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="HDTBannerLabelBrush" Color="#FFD6E9F5" /> <SolidColorBrush x:Key="HDTPanelBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="HDTPanelTextBrush" Color="#FF1B1B1B" /> <SolidColorBrush x:Key="HDTBorderBrush" Color="#FFC8C8C8" /> <SolidColorBrush x:Key="HDTLabelBrush" Color="#FF0E639C" /> <SolidColorBrush x:Key="HDTCommandBrush" Color="#FFF7F7F7" /> <SolidColorBrush x:Key="HDTCommandTextBrush" Color="#FF0A3069" /> <SolidColorBrush x:Key="HDTFieldBrush" Color="#FFEDEDED" /> <SolidColorBrush x:Key="HDTHintTextBrush" Color="#FF6B6B6B" /> <SolidColorBrush x:Key="HDTErrorBrush" Color="#FFA31515" /> <SolidColorBrush x:Key="HDTFooterBrush" Color="#FFE8E8E8" /> <SolidColorBrush x:Key="HDTButtonBrush" Color="#FF0E639C" /> <SolidColorBrush x:Key="HDTButtonTextBrush" Color="#FFFFFFFF" /> <SolidColorBrush x:Key="HDTButtonHoverBrush" Color="#FFCCE4F7" /> <SolidColorBrush x:Key="HDTButtonHoverTextBrush" Color="#FF000000" /> <SolidColorBrush x:Key="HDTButtonPressedBrush" Color="#FFA9CFEC" /> <!-- THE ? THIS TOOLKIT'S PAGES USE, in the same shape as the New Application and Windows PE windows: a Border rather than a bare glyph, because WPF raises a tooltip only over what is painted. --> <Style TargetType="Border" x:Key="HDTHelpDot"> <Setter Property="Background" Value="Transparent" /> <Setter Property="Width" Value="22" /> <Setter Property="Height" Value="22" /> <Setter Property="CornerRadius" Value="11" /> <Setter Property="BorderBrush" Value="{DynamicResource HDTBorderBrush}" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="HorizontalAlignment" Value="Right" /> <Setter Property="VerticalAlignment" Value="Top" /> <Setter Property="Cursor" Value="Help" /> <Setter Property="Margin" Value="8,2,0,0" /> <Setter Property="ToolTipService.ShowDuration" Value="60000" /> <Setter Property="ToolTipService.InitialShowDelay" Value="200" /> </Style> <Style TargetType="TextBlock" x:Key="HDTHelpGlyph"> <Setter Property="Text" Value="?" /> <Setter Property="Foreground" Value="{DynamicResource HDTHintTextBrush}" /> <Setter Property="FontSize" Value="12" /> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" /> </Style> <!-- THE BUTTON CARRIES ITS OWN TEMPLATE, AND ITS COLOURS ARE SETTERS RATHER THAN LOCAL VALUES. Two WPF traps meet on this control. A local Foreground="White" on the Button element beats any style trigger, so a hover rule written that way silently does nothing - the wizard workstream hit exactly that. And the stock button template ignores Background on hover entirely, painting its own wash, so a Setter alone cannot fix the pairing either. The template below is the only arrangement where "light wash, dark label" actually reaches the screen; Get-HDTConsoleTheme decides which colours those are, and a test measures the contrast of the pair in both palettes. --> <Style TargetType="Button"> <Setter Property="Background" Value="{DynamicResource HDTButtonBrush}" /> <Setter Property="Foreground" Value="{DynamicResource HDTButtonTextBrush}" /> <Setter Property="BorderThickness" Value="0" /> <Setter Property="FontSize" Value="13" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border x:Name="HDTButtonSurface" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" /> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="HDTButtonSurface" Property="Background" Value="{DynamicResource HDTButtonHoverBrush}" /> <Setter Property="Foreground" Value="{DynamicResource HDTButtonHoverTextBrush}" /> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter TargetName="HDTButtonSurface" Property="Background" Value="{DynamicResource HDTButtonPressedBrush}" /> <Setter Property="Foreground" Value="{DynamicResource HDTButtonHoverTextBrush}" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> <!-- A DROPDOWN THAT DOES NOT LOOK READ-ONLY. WPF's default ComboBox template paints its own grey chrome and IGNORES Background, so a combo sitting beside white editable boxes and grey read-only ones reads as one of the grey ones - which is how the Log level dropdown came to look disabled while it was not. IsEditable puts a real text box in the closed control, which takes the Background above; IsReadOnly keeps it from being typed into, so it still behaves exactly like a dropdown - and the value can now be selected and copied, which the old one refused. NO ComboBox HERE USES AN ItemTemplate, so what the text box shows is what the closed box showed before: DisplayMemberPath where one is declared, ToString() otherwise. --> <Style TargetType="ComboBox"> <Setter Property="IsEditable" Value="True" /> <Setter Property="IsReadOnly" Value="True" /> <Setter Property="Background" Value="{DynamicResource HDTPanelBrush}" /> <Setter Property="Foreground" Value="{DynamicResource HDTPanelTextBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource HDTBorderBrush}" /> <Setter Property="BorderThickness" Value="1" /> <Setter Property="Padding" Value="6,4,6,4" /> </Style> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="64" /> </Grid.RowDefinitions> <!-- banner: the share, and BOTH of its paths --> <Border Grid.Row="0" Background="{DynamicResource HDTBannerBrush}" Padding="24,16,24,16"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" x:Name="HDTShareText" Foreground="{DynamicResource HDTBannerTextBrush}" FontSize="21" FontWeight="SemiBold" TextTrimming="CharacterEllipsis" /> <TextBlock Grid.Row="1" Grid.Column="0" x:Name="HDTConsoleDeployRootLabel" Foreground="{DynamicResource HDTBannerLabelBrush}" FontSize="12" Margin="0,10,14,0" Width="86" /> <TextBlock Grid.Row="1" Grid.Column="1" x:Name="HDTDeployRootText" Text="" Foreground="{DynamicResource HDTBannerTextBrush}" FontSize="12" FontFamily="Consolas, Courier New" Margin="0,10,0,0" TextTrimming="CharacterEllipsis" /> <TextBlock Grid.Row="2" Grid.Column="0" x:Name="HDTConsoleOpenedFromLabel" Foreground="{DynamicResource HDTBannerLabelBrush}" FontSize="12" Margin="0,4,14,0" Width="86" /> <TextBlock Grid.Row="2" Grid.Column="1" x:Name="HDTRootText" Text="" Foreground="{DynamicResource HDTBannerTextBrush}" FontSize="12" FontFamily="Consolas, Courier New" Margin="0,4,0,0" TextTrimming="CharacterEllipsis" /> </Grid> </Border> <!-- body: the tree on the left, the selected row on the right --> <Grid Grid.Row="1" Margin="24,20,24,0"> <Grid.ColumnDefinitions> <!-- BOTH SIDES ARE STAR-SIZED so the splitter can give width to either one. A fixed left column and a star right column would let the splitter grow the tree and never the details, which is the direction nobody needs: the pane is where the long text is. The MinWidths stop either side being dragged shut, because a pane dragged to nothing looks like a bug rather than a choice. --> <ColumnDefinition Width="420*" MinWidth="220" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="740*" MinWidth="260" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" x:Name="HDTConsoleTreeLabel" Foreground="{DynamicResource HDTLabelBrush}" FontSize="12" Margin="0,0,0,6" /> <TreeView Grid.Row="1" Grid.Column="0" x:Name="HDTConsoleTree" Background="{DynamicResource HDTPanelBrush}" Foreground="{DynamicResource HDTPanelTextBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1" FontFamily="Segoe UI" FontSize="13" ScrollViewer.HorizontalScrollBarVisibility="Disabled"> <!-- MDT'S New Task Sequence, WHERE MDT PUTS IT: a right-click on the Task Sequences node. A button in the footer was the wrong shape - this is an action ON a node, and the footer is where a window's own actions live. THE ITEM IS DARK UNLESS THAT NODE IS THE ONE UNDER THE POINTER, and the host tells which node that is by NAME - the label carries a count and would put a parser in the window. --> <TreeView.ContextMenu> <!-- ONE MENU, AND THE ROW DECIDES WHICH ITEMS IT HAS. New Task Sequence belongs to the Task Sequences category; Remove belongs to a sequence. Items that do not apply are collapsed rather than greyed, and a row with nothing to offer opens no menu at all - a menu that appears everywhere with one live item teaches that right-click does nothing here, on the rows where it does something. --> <ContextMenu x:Name="HDTConsoleTreeMenu"> <!-- WORKBENCH'S ROOT NODE ACTIONS. Its tree roots every share under Deployment Shares and hangs New and Open off that row; HDT's root had no menu at all, so the only way to add a share was to name it on the command line and the only way to make one was New-HDTWorkspace at a prompt. CLOSE IS ON THE SHARE ITSELF, because that is the row somebody means. It takes the share out of this window and off the remembered list - it deletes nothing. --> <MenuItem x:Name="HDTNewWorkspaceMenuItem" /> <MenuItem x:Name="HDTOpenWorkspaceMenuItem" /> <MenuItem x:Name="HDTCloseWorkspaceMenuItem" /> <!-- THE ONE ACTION THAT BUILDS A WinPE, AND IT WAS BEHIND A DOUBLE-CLICK. The Windows PE window opened only from the image row under Boot Image - the row that reads 'not built' before there is an image at all - so the first thing an administrator has to do with a new share was the hardest thing in this console to find. It hangs off both rows now, which is where MDT puts a share's Properties. --> <MenuItem x:Name="HDTBootImageMenuItem" /> <MenuItem x:Name="HDTNewSequenceMenuItem" /> <MenuItem x:Name="HDTRemoveSequenceMenuItem" /> <MenuItem x:Name="HDTImportOperatingSystemMenuItem" /> <MenuItem x:Name="HDTRemoveOperatingSystemMenuItem" /> <MenuItem x:Name="HDTNewApplicationMenuItem" /> <MenuItem x:Name="HDTRemoveApplicationMenuItem" /> <!-- PICKED, NOT TYPED. A dependency is an application id in a document, and a misspelled one is not caught until a deployment runs - when Resolve-HDTApplicationOrder refuses the whole plan rather than that one application. --> <MenuItem x:Name="HDTApplicationDependencyMenuItem" /> <MenuItem x:Name="HDTApplicationDetectionMenuItem" /> <!-- WORKBENCH'S FOLDERS. MDT nests task sequences and operating systems into real directories under Control\; HDT's are a label on the document, because the directory a sequence sits in is part of the path the engine resolves its id from - so moving one between folders would break every rule and boot image that names it. NEW FOLDER SITS ON THE CATEGORY AND ON A FOLDER, which is the only difference between making one at the top and making one inside another. Move To belongs to an item and Delete to a folder with nothing in it - Get-HDTConsoleFolderAction is where that is decided, and it is decided there so that it can be tested. --> <Separator x:Name="HDTFolderMenuSeparator" /> <MenuItem x:Name="HDTNewFolderMenuItem" /> <MenuItem x:Name="HDTMoveToFolderMenuItem" /> <MenuItem x:Name="HDTDeleteFolderMenuItem" /> </ContextMenu> </TreeView.ContextMenu> <TreeView.ItemContainerStyle> <Style TargetType="TreeViewItem"> <!-- Which branches start open is the row's decision. --> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> <Setter Property="Foreground" Value="{DynamicResource HDTPanelTextBrush}" /> <Setter Property="Padding" Value="2,3,2,3" /> <!-- WITHOUT THIS THE ROW ANNOUNCES ITSELF AS A DUMP OF THE WHOLE OBJECT. A TreeViewItem bound to data with no AutomationProperties.Name falls back to the item's ToString(), and a PSCustomObject's ToString() is every property it has - Depth, Kind, the entire Detail text, the icon, the header. A screen reader reads that blob instead of "Boot Image", and so does any automation that looks a row up by name. Found by driving the window through UI Automation; nothing on screen looks wrong. --> <Setter Property="AutomationProperties.Name" Value="{Binding Text}" /> </Style> </TreeView.ItemContainerStyle> <TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Children}"> <StackPanel Orientation="Horizontal"> <!-- COLOURED, AND THE COLOUR IS THE ROW'S. Bound rather than themed: see Get-HDTConsoleIconColor for why a per-row DynamicResource key is not possible in markup XamlReader loads. WPF converts the string to a Brush through the target property's own type converter. --> <TextBlock Text="{Binding Icon}" Foreground="{Binding IconColor}" FontFamily="Segoe UI Emoji, Segoe UI Symbol" FontSize="13" Margin="0,0,7,0" VerticalAlignment="Center" /> <TextBlock Text="{Binding Text}" VerticalAlignment="Center" TextTrimming="CharacterEllipsis" /> </StackPanel> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> <!-- The handle between them. It spans the label row as well as the tree, so the grip runs the full height of the two panes and there is no dead strip above it to grab at by mistake. --> <GridSplitter Grid.Row="0" Grid.RowSpan="2" Grid.Column="1" x:Name="HDTSplitter" Width="8" Margin="6,0,6,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="{DynamicResource HDTBorderBrush}" ShowsPreview="False" /> <TextBlock Grid.Row="0" Grid.Column="2" x:Name="HDTConsoleDetailLabel" Foreground="{DynamicResource HDTLabelBrush}" FontSize="12" Margin="0,0,0,6" /> <!-- A PROPERTIES SHEET, NOT A PARAGRAPH. One labelled box per fact, the way Deployment Workbench shows a selected item: each value can be selected and copied on its own, long ones wrap instead of running off the edge, and the layout is already the shape an editor needs when C2 makes these writable. WHICH BOXES TAKE TYPING IS THE ROW'S DECISION, not this file's: IsReadOnly is bound to the row, and New-HDTConsoleField sets it from whether the row names a key in the document. A task sequence's name and description do; a step count, a validation result and a path do not, and a box that took typing and threw it away would be worse than one that plainly does not. --> <Border Grid.Row="1" Grid.Column="2" Background="{DynamicResource HDTPanelBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1"> <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" Padding="14,12,14,12"> <ItemsControl x:Name="HDTDetailList"> <ItemsControl.ItemTemplate> <DataTemplate> <Grid Margin="0,0,0,8"> <Grid.ColumnDefinitions> <ColumnDefinition Width="150" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding Label}" Foreground="{DynamicResource HDTPanelTextBrush}" FontFamily="Segoe UI" FontSize="12" TextWrapping="Wrap" Margin="0,5,10,0" VerticalAlignment="Top" /> <TextBox Grid.Column="1" x:Name="HDTDetailBox" Text="{Binding Value}" IsReadOnly="{Binding ReadOnly}" AcceptsReturn="True" TextWrapping="Wrap" Background="{DynamicResource HDTPanelBrush}" Foreground="{DynamicResource HDTPanelTextBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1" FontFamily="Consolas, Courier New" FontSize="12" Padding="6,4,6,4" /> <!-- THE SAME COLUMN, ONE OF THE TWO SHOWN. A row whose document allows a closed set gets the set, not a box that names none of it: Log level accepts four words, and the only way to learn them from a box was to type something, save, and read the refusal. SelectedItem BINDS TO Value, the same property the TextBox binds to, because Apply diffs Value against Original and neither knows which control produced the string. A combo that published its pick anywhere else would light Apply and then write the old value. COLLAPSED BY DEFAULT so the ordinary row pays nothing for this; the trigger below swaps the pair over. --> <ComboBox Grid.Column="1" x:Name="HDTDetailChoice" Visibility="Collapsed" ItemsSource="{Binding Choice}" SelectedItem="{Binding Value, Mode=TwoWay}" Background="{DynamicResource HDTPanelBrush}" Foreground="{DynamicResource HDTPanelTextBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1" FontFamily="Segoe UI" FontSize="12" Padding="6,4,6,4" HorizontalAlignment="Left" MinWidth="180" /> <!-- THE ? THE OTHER PAGES USE, on the row that needs one and invisible on every row that does not. A hint under each box would make this pane a manual; behind a dot, the technician gets the sentence when they ask for it. WHAT IT IS FOR, IN ONE CASE: an install command line is handed to cmd.exe with the application's own folder as the working directory. Nothing on the row can show that, and typing %~dp0 into it - which expands only inside a .cmd file - fails as a missing file rather than as the mistake it is. --> <Border Grid.Column="2" x:Name="HDTDetailHelpDot" Style="{StaticResource HDTHelpDot}"> <Border.ToolTip> <ToolTip MaxWidth="380"> <TextBlock TextWrapping="Wrap" Text="{Binding Hint}" /> </ToolTip> </Border.ToolTip> <TextBlock Style="{StaticResource HDTHelpGlyph}" /> </Border> </Grid> <!-- WHITE MEANS TYPE HERE, and it is the only thing on this pane that says so: seven identical boxes of which two accept a rename is a pane nobody discovers. The editor's Properties rows are painted the same way and by the same means. A TRIGGER INSIDE THE TEMPLATE, NOT A STYLE. A local Background beats a Style trigger in WPF - the trap the wizard workstream hit with Foreground - but a template trigger naming the element wins. --> <DataTemplate.Triggers> <DataTrigger Binding="{Binding ReadOnly}" Value="True"> <Setter TargetName="HDTDetailBox" Property="Background" Value="{DynamicResource HDTFieldBrush}" /> </DataTrigger> <!-- COLLAPSED, NOT HIDDEN: a row with no hint gives its width back to the box rather than leaving a hole where the dot would be. --> <DataTrigger Binding="{Binding HasHint}" Value="False"> <Setter TargetName="HDTDetailHelpDot" Property="Visibility" Value="Collapsed" /> </DataTrigger> <!-- ONE CONTROL OR THE OTHER, NEVER BOTH. They share a column, so leaving the box visible under the list would paint the value twice and let the dead one take the click. --> <DataTrigger Binding="{Binding HasChoice}" Value="True"> <Setter TargetName="HDTDetailBox" Property="Visibility" Value="Collapsed" /> <Setter TargetName="HDTDetailChoice" Property="Visibility" Value="Visible" /> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </ScrollViewer> </Border> <!-- DESIGN 12: "the console shows that invocation - so an admin can learn the automation surface by clicking around, and script anything they can do in the UI". Selectable, so it can be copied. --> <Grid Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Margin="0,16,0,0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" x:Name="HDTConsoleCommandLabel" Foreground="{DynamicResource HDTLabelBrush}" FontSize="12" VerticalAlignment="Center" Margin="0,0,12,0" /> <TextBox Grid.Column="1" x:Name="HDTCommandText" Text="" IsReadOnly="True" TextWrapping="NoWrap" Background="{DynamicResource HDTCommandBrush}" Foreground="{DynamicResource HDTCommandTextBrush}" BorderBrush="{DynamicResource HDTBorderBrush}" BorderThickness="1" FontFamily="Consolas, Courier New" FontSize="13" Padding="8,6,8,6" HorizontalScrollBarVisibility="Auto" /> </Grid> </Grid> <!-- footer --> <Border Grid.Row="2" Background="{DynamicResource HDTFooterBrush}" Margin="0,20,0,0"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,24,0"> <!-- No local Background or Foreground: they are Setters in the style above, because a local value would beat the hover trigger and the label would stay white on a light wash. --> <!-- APPLY WRITES WHAT IS TYPED IN THE DETAIL PANE, and it is the only button on this window that touches a share. It is dark until there is something to write: a row whose value differs from what it was built with, on a task sequence. IT IS NEEDED EVEN THOUGH A BOX WRITES WHEN IT LOSES FOCUS. The last box somebody types in is the one still holding the caret when they look for a button - so an editor with no Apply asks them to click elsewhere before their change is real, which is a rule nobody can see. --> <Button x:Name="HDTApplyButton" Width="112" Height="34" Margin="0,0,10,0" IsEnabled="False" /> <Button x:Name="HDTCloseButton" Width="112" Height="34" IsDefault="True" IsCancel="True" /> </StackPanel> </Border> </Grid> </Window> |