10 KiB
Control Templates
Control templates change controls' outfit, and they are working based on data bindings.
Controls have their own control template types. For example, SelectableButton needs SelectableButtonTemplate. Any derived classes of SelectableButtonTemplate is acceptable, but extra members will be ignored.
A control template has input properties and output properties. Input properties are for data binding, when the control's state is changed, these properties will be changed, and the control template reacts to these properties and changes how it looks, for example, some colors. Output properties are configuration to the control's outfit related behavior, almost all of them will only be read once, so changing them at runtime takes no effect.
Here we demo how to write a control template for SelectableButton. When the mouse is hovering on the button, it becomes brighter. When the button is selected, it becomes less brighter.
These are properties in ControlTemplate: - Input Properties: - Focused: True when the control is focused. - VisuallyEnabled: Trhe when the control is enabled. It requires all parent controls to be enabled. - Font: The control's font. If a font is not associated to the control, it will be the font from a parent control. - Context: The control's Context property. - Text: The control's Text property. - Output Properties: - ContainerComposition: A composition for control's children, anything put in <SelectableButton></SelectableButton> will be in this composition. - FocusableComposition: A composition for receiving keyboard events. It works only when the control is focusable.
These are properties in ButtonTemplate and SelectableButtonTemplate: - Input Properties: - State: The button's state, can be Normal, Active or Pressed. - Selected: True when the button is selected.
We can use all of them to create a control template for SelectableButton: <Instance ref.Class="sample::TabHeaderButtonTemplate"> <SelectableButtonTemplate ref.Name="self" MinSizeLimitation="LimitToElementAndChildren"> <Bounds AlignmentToParent="left:0 top:0 right:0 bottom:0"> <SolidBackground> <att.Color-bind><![CDATA[ cast Color ( not self.VisuallyEnabled ? "#00000000" : self.State == ButtonState::Pressed ? "#3D3D3D" : self.State == ButtonState::Active ? "#808080" : self.Selected ? "#3D3D3D" : "#00000000" ) ]]></att.Color-bind> </SolidBackground> </Bounds> <Bounds AlignmentToParent="left:0 top:0 right:0 bottom:0"> <att.Visible-bind>self.Focused</att.Visible-bind> <FocusRectangle/> </Bounds> <Bounds AlignmentToParent="left:1 top:1 right:1 bottom:1" MinSizeLimitation="LimitToElement"> <SolidLabel Text-bind="self.Text" Font-bind="self.Font"> <att.Color-bind><![CDATA[ cast Color ( self.VisuallyEnabled ? "#FFFFFF" : "808080" ) ]]></att.Color-bind> </SolidLabel> </Bounds> </SelectableButtonTemplate> </Instance>
And then a window containing three check boxes with this control template: <Instance ref.Class="sample::MainWindow" ref.Styles="res://MainWindow/SharedStyle"> <Window Text="kb_ctemplates" IconVisible="true" ClientSize="x:640 y:480"> <att.BoundsComposition-set PreferredMinSize="x:480 y:320"/> <Stack Direction="Vertical" MinSizeLimitation="LimitToElementAndChildren" AlignmentToParent="left:5 top:5 right:-1 bottom:-1" Padding="5"> <StackItem><CheckBox Text="Button 1"/></StackItem> <StackItem><CheckBox Text="Button 2"/></StackItem> <StackItem><CheckBox Text="Button 3"/></StackItem> </Stack> </Window> </Instance>
With a style to assign control templates to all check boxes: <Styles> <Style ref.Path="//CheckBox"> <att.ControlTemplate>sample::TabHeaderButtonTemplate</att.ControlTemplate> <att.BoundsComposition-set AlignmentToParent="left:0 top:0 right:0 bottom:0"/> </Style> </Styles>
Finally we get:
With this new control template, we can create a more complex control template for tab control.
Tab needs TabTemplate, and it has two important properties: Commands and TabPages. These are all input properties. Commands is an interface with a method ShowTab for activate a tab page. TabPages is an observable collection for all tab pages at runtime.
By binding TabPages to a RepeatStack, we can easily create a list of buttons for each tab pages. When tab pages are inserted to or removed from the tab control, the list of buttons will be automatically refreshed.
Firstly we create a control template for Tab: <Instance ref.Class="sample::TabTemplate"> <TabTemplate ref.Name="self" ContainerComposition-ref="container" TabOrder="TopToBottom" MinSizeLimitation="LimitToElementAndChildren"> <Table AlignmentToParent="left:0 top:0 right:0 bottom:0" MinSizeLimitation="LimitToElementAndChildren"> <att.Rows> <CellOption>composeType:Percentage percentage:1.0</CellOption> </att.Rows> <att.Columns> <CellOption>composeType:MinSize</CellOption> <CellOption>composeType:Percentage percentage:1.0</CellOption> </att.Columns> <Cell Site="row:0 column:0"> <RepeatStack ref.Name="header" AlignmentToParent="left:0 top:0 right:0 bottom:0" Padding="2" Direction="Vertical" MinSizeLimitation="LimitToElementAndChildren"> <att.ItemTemplate>sample::TabHeaderTemplate</att.ItemTemplate> <att.ItemSource-bind>self.TabPages</att.ItemSource-bind> <ev.ItemInserted-eval> <![CDATA[ { var stackItem = header.StackItems[arguments.itemIndex]; var item = cast (TabHeaderTemplate*) stackItem.Children[0]; item.Commands = self.Commands; } ]]> </ev.ItemInserted-eval> </RepeatStack> </Cell> <Cell Site="row:0 column:1"> <SolidBackground Color="#3D3D3D"/> <Bounds ref.Name="container" AlignmentToParent="left:1 top:1 right:1 bottom:1"/> </Cell> </Table> </TabTemplate> </Instance> In this control template, we create a list of buttons using RepeatStack on the left side, and create a container for tab page contents on the right side.
The item template in RepeatStack is named sample::TabHeaderTemplate, which is: <Instance ref.Class="sample::TabHeaderTemplate"> <ref.Parameter Name="CurrentTabPage" Class="presentation::controls::GuiTabPage"/> <ref.Members> <![CDATA[ prop Commands : ITabCommandExecutor* = null {} ]]> </ref.Members> <ControlTemplate ref.Name="self" MinSizeLimitation="LimitToElementAndChildren"> <CheckBox AutoFocus="false" AutoSelection="false"> <att.ControlTemplate>sample::TabHeaderButtonTemplate</att.ControlTemplate> <att.BoundsComposition-set AlignmentToParent="left:0 top:0 right:0 bottom:0"/> <att.Context-ref>CurrentTabPage</att.Context-ref> <att.Text-bind>CurrentTabPage.Text</att.Text-bind> <att.Alt-bind>CurrentTabPage.Alt</att.Alt-bind> <att.Selected-bind>CurrentTabPage.OwnerTab.SelectedPage == CurrentTabPage</att.Selected-bind> <ev.Clicked-eval> <![CDATA[ { self.Commands.ShowTab(CurrentTabPage.OwnerTab.Pages.IndexOf(CurrentTabPage), true); } ]]> </ev.Clicked-eval> </CheckBox> </ControlTemplate> </Instance>
sample::TabHeaderTemplate has a Commands property, it needs to be assigned in sample::TabTemplate, so that when a tab header is clicked, it can tell the tab control to switch to that tab.
sample::TabTemplate reacts to ItemInserted event in RepeatStack, by accessing arguments.itemIndex which is provided by the event, it knows a new item template is created, and grab that item template to set the Commands property.
This ensures that whenever a new item template for the tab header appears in the tab control, the Commands property has already been assigned.
In RepeatStack there is <att.ItemSource-bind>self.TabPages</att.ItemSource-bind>. This code binds TabPages to RepeatStack. And <att.ItemTemplate>sample::TabHeaderTemplate</att.ItemTemplate> tells it to create sample::TabHeaderTemplate for each tab page.
The assigned tab page for a particular sample::TabHeaderTemplate will be in ref.Parameter, which is a readonly property and constructor argument.
Knowledge base for item templates will be introduces in the next chapter.
Finally, we create a window with three tab pages to see how it works: <Folder name="MainWindow"> <InstanceStyle name="SharedStyle"> <Styles> <Style ref.Path="//Stack" Direction="Vertical" MinSizeLimitation="LimitToElementAndChildren"/> <Style ref.Path="//RadioButton" GroupController-ref="groupDirection"/> <Style ref.Path="//TabPage"> <att.ContainerComposition-set InternalMargin="left:10 top:10 right:10 bottom:10"/> </Style> </Styles> </InstanceStyle> <Instance name="MainWindowResource"> <Instance ref.Class="sample::MainWindow" ref.Styles="res://MainWindow/SharedStyle" xmlns:x="presentation::controls::GuiSelectableButton::*"> <Window Text="kb_ctemplates" IconVisible="true" ClientSize="x:640 y:480"> <att.BoundsComposition-set PreferredMinSize="x:480 y:320"/> <x:MutexGroupController ref.Name="groupDirection"/> <Tab> <att.ControlTemplate>sample::TabTemplate</att.ControlTemplate> <att.BoundsComposition-set AlignmentToParent="left:0 top:0 right:0 bottom:0"/> <att.Pages> <TabPage Text="First TabPage"> <Button Text="A button in the first page"/> </TabPage> <TabPage Text="Second TabPage"> <Stack> <StackItem><CheckBox Text="A check box in the second page"/></StackItem> <StackItem><CheckBox Text="A check box in the second page"/></StackItem> <StackItem><CheckBox Text="A check box in the second page"/></StackItem> </Stack> </TabPage> <TabPage Text="Third TabPage"> <Stack> <StackItem><RadioButton Text="A radio button in the third page"/></StackItem> <StackItem><RadioButton Text="A radio button in the third page"/></StackItem> <StackItem><RadioButton Text="A radio button in the third page"/></StackItem> </Stack> </TabPage> </att.Pages> </Tab> </Window> </Instance> </Instance> </Folder>
Here we have a button for the first tab page, three check boxes for the second tab page, and three radio buttons for the third tab page. Each radio button is assigned the same group controller so that they know they are exclusive to each other. By assigning different group controllers to radio buttons, it is very easy to tell these radio buttons who are their allies.
Finally we get:
- Source code: kb_ctemplates -

