Table of Contents

Class Repeater

Namespace
BSGameLib.UI.Controls
Assembly
BSGameLib.UI.dll

Builds one copy of an item template per item in ItemsSource, and lets the player pick one.

public class Repeater : InteractiveControl, IUpdateable, IEquatable<IUpdateable>, IDrawable, IIdentifiable<Guid>, IEquatable<IDrawable>, IDisposable, IFocusScope
Inheritance
Repeater
Implements
Inherited Members

Remarks

An InteractiveControl that *owns* a StackLayout rather than being one, because a control can only have one base and rows need both: input (to select a row) and real layout (to arrange it). Composing gets the arrange/measure behaviour from the same code every other stack uses, without a parallel implementation to keep in step.

Keeping the rows out of the view's own control tree is deliberate, not a side effect. Generated rows share the template's control ids, so a tree walk for GetControl("name") would find an arbitrary one of N. That used to cost the rows their input as well, since a view collected its focusable controls once, at initialise, and rows arrive after that - so only row selection and activation worked, and a button inside a template was inert. IFocusScope is the way back in: the rows stay out of the tree that resolves ids and view-level bindings, and the view asks this control separately for the controls inside them.

Overflow is handled by scrolling whole rows in and out of view, not by clipping: nothing below IRenderEncoderScope exposes a scissor rect, so a partially drawn row would spill outside the list. Same trade-off, and the same reasoning, as the ComboBox dropdown.

Constructors

Repeater(ILogger, View, string)

Creates an empty list in view.

public Repeater(ILogger logger, View view, string id = null)

Parameters

logger ILogger
view View
id string

Properties

ActivateCommand

Runs when a row is double-clicked, or Enter is pressed with a row selected.

public ICommand ActivateCommand { get; set; }

Property Value

ICommand

CanFocus

Whether the list takes focus: true when it has an ActivateCommand or a binding to SelectedItem. A list that only shows its items is passed over by Tab.

public override bool CanFocus { get; }

Property Value

bool

CanScroll

True while there are more rows than currently fit.

public bool CanScroll { get; }

Property Value

bool

FocusColor

Outline around the selected row, or the whole list when nothing is selected, while the list has focus. Without it a focused list looks exactly like an unfocused one, and a keyboard user cannot tell the arrow keys will move it.

public Color? FocusColor { get; set; }

Property Value

Color?

HoverColor

Fill drawn behind the row under the pointer.

public Color? HoverColor { get; set; }

Property Value

Color?

ItemSpacing

The gap between rows, in pixels.

public float ItemSpacing { get; set; }

Property Value

float

Items

The item of every row, in order, as a new list. Rows that are not shown are included.

public IReadOnlyList<object> Items { get; }

Property Value

IReadOnlyList<object>

ItemsSource

The collection to repeat over. Assigning a different collection rebuilds every row; re-assigning the same instance does nothing. Call Refresh() after mutating a collection in place, since there is no collection-change notification anywhere in this UI layer to hook.

public IEnumerable ItemsSource { get; set; }

Property Value

IEnumerable

Remarks

The reference check is load-bearing, not an optimisation. A view refreshes every binding whenever the view model raises any property change, so selecting a row - which writes the selection back to the view model - re-assigned this property in the same breath. Rebuilding here threw away the rows, cleared the selection that had just been made, and published that empty selection straight back over the one the click set. The visible result was a list that could not be selected at all.

MaxExtent

Largest the list may grow along its orientation, in pixels. 0 lets it size to its contents.

public float MaxExtent { get; set; }

Property Value

float

Remarks

A declared Size on the same axis wins, and forces exactly that extent. The difference matters: a fixed height leaves dead space below the last row whenever there are fewer rows than fit, which is the usual case for a directory that happens to be quiet. A cap grows with the content and only starts scrolling once it is actually exceeded.

Orientation

Direction rows are laid out in.

public Orientation Orientation { get; set; }

Property Value

Orientation

Padding

The space kept clear inside the list, around the rows.

public Spacing Padding { get; set; }

Property Value

Spacing

RequiresRawKeyboard

True, so that the arrow keys, Home, End, PageUp, PageDown and Enter reach the list while it has focus.

public override bool RequiresRawKeyboard { get; }

Property Value

bool

ScrollIndex

Index of the first row shown, for a list scrolled past its start.

public int ScrollIndex { get; }

Property Value

int

ScrollIndicatorColor

Thumb shown when there are more rows than fit.

public Color? ScrollIndicatorColor { get; set; }

Property Value

Color?

SelectedIndex

The index of the selected row, or -1 for none. An index outside the rows counts as none. Setting it scrolls the row into view and raises SelectionChanged.

public int SelectedIndex { get; set; }

Property Value

int

SelectedItem

The selected row's item, or null. Settable so a two-way "{Binding SelectedServer}" round-trips: the view model pushing a value in selects the matching row.

public object SelectedItem { get; set; }

Property Value

object

SelectionColor

Fill drawn behind the selected row.

public Color? SelectionColor { get; set; }

Property Value

Color?

SelectionMarkerColor

A marker down the selected row's leading edge.

public Color? SelectionMarkerColor { get; set; }

Property Value

Color?

SelectionMarkerThickness

The width of the marker down the selected row's leading edge, in pixels. 2 by default.

public int SelectionMarkerThickness { get; set; }

Property Value

int

Stretch

Take the size the parent layout gives this list instead of sizing to the rows: rows are laid out as wide as it, and scroll within its length.

public bool Stretch { get; set; }

Property Value

bool

Remarks

Off by default because it only works where the parent does decide the size - the fill of a dock layout. A stack sizes a list from the list's own answer, so a stretched list in one would never grow past its first size.

SuppressesDefaultButton

True while a row is selected and there is an ActivateCommand, so that Enter activates the row and the view's default button does not also run.

public override bool SuppressesDefaultButton { get; }

Property Value

bool

VisibleItemCount

How many rows currently fit, and so are laid out and drawn.

public int VisibleItemCount { get; }

Property Value

int

Methods

Dispose()

Disposes every row and the list's panel, then the list.

public override void Dispose()

Draw(IGameTime)

Draws the list's fill and border, then its visible rows, the selection, the hover highlight, the focus outline and the scroll indicator.

public override void Draw(IGameTime gameTime)

Parameters

gameTime IGameTime

ItemAt(Vector2)

The item of the shown row at point, in UI space, or null.

public object ItemAt(Vector2 point)

Parameters

point Vector2

Returns

object

LoadContent()

Loads the content of each row.

public override void LoadContent()

Refresh()

Rebuilds every row from the current ItemsSource - for a collection whose contents changed without the collection itself being replaced.

public void Refresh()

Update(IGameTime)

Lays the rows out again when the list moves or changes width, then updates the rows.

public override void Update(IGameTime gameTime)

Parameters

gameTime IGameTime

Events

RowBuilt

Raised for each row as it is built, with a way to reach the controls inside it.

public event EventHandler<RepeaterRowEventArgs> RowBuilt

Event Type

EventHandler<RepeaterRowEventArgs>

Remarks

For what a row cannot say as a binding -- a sprite-sheet cell, a generated texture -- since rows are kept out of the view's control tree and every row shares the template's ids. Fires on every rebuild, so a handler runs again for rows that are logically the same item; it must be safe to call twice and must not assume it is seeing a row for the first time.

SelectionChanged

Raised when SelectedIndex changes, including when the selection is cleared.

public event EventHandler SelectionChanged

Event Type

EventHandler