-1

I'm creating a Wizard control based on Selector. My plan is that it will behave in the similar way to TabControl, ListBox, etc. - by having its own child items.

What I already did is:

public class Wizard : Selector
{
    // ...

    protected override DependencyObject GetContainerForItemOverride() => new WizardPage();

    protected override bool IsItemItsOwnContainerOverride(object item) => item is WizardPage;

    // ...

The relevant part ControlTemplate for my control looks like this:

<Style TargetType="{x:Type c:Wizard}">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <Grid />
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type c:Wizard}">
                <DockPanel Background="{TemplateBinding Background}"
                    Margin="{TemplateBinding Padding}">
                    <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right">
                        <Button Name="BackButton" MinWidth="75" Command="{x:Static c:WizardCommands.PreviousPage}"
                        Content="{TemplateBinding BackButtonContent}" Style="{StaticResource WizardButton}">
                            <Button.Visibility>
                                <MultiBinding Converter="{StaticResource WizardPageButtonVisibilityConverter}">
                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="BackButtonVisibility" />
                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="SelectedItem.BackButtonVisibility" />
                                </MultiBinding>
                            </Button.Visibility>
                        </Button>
                        <Button Name="NextButton" Command="{x:Static c:WizardCommands.NextPage}"
                        Content="{TemplateBinding NextButtonContent}" Style="{StaticResource WizardButton}">
                            <Button.Visibility>
                                <MultiBinding Converter="{StaticResource WizardPageButtonVisibilityConverter}">
                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="NextButtonVisibility" />
                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="SelectedItem.NextButtonVisibility" />
                                </MultiBinding>
                            </Button.Visibility>
                        </Button>
                        <Button Name="FinishButton" Command="{x:Static c:WizardCommands.Finish}" Content="{TemplateBinding FinishButtonContent}"
                        Style="{StaticResource WizardButton}">
                            <Button.Visibility>
                                <MultiBinding Converter="{StaticResource WizardPageButtonVisibilityConverter}">
                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FinishButtonVisibility" />
                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="SelectedItem.FinishButtonVisibility" />
                                </MultiBinding>
                            </Button.Visibility>
                        </Button>
                        <Button Name="CancelButton" Command="{x:Static c:WizardCommands.Cancel}" Content="{TemplateBinding CancelButtonContent}"
                        Style="{StaticResource WizardButton}">
                            <Button.Visibility>
                                <MultiBinding Converter="{StaticResource WizardPageButtonVisibilityConverter}">
                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="CancelButtonVisibility" />
                                    <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="SelectedItem.CancelButtonVisibility" />
                                </MultiBinding>
                            </Button.Visibility>
                        </Button>
                    </StackPanel>

                    <ContentPresenter Content="{Binding SelectedItem, RelativeSource={RelativeSource TemplatedParent}}" />
                </DockPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

However, when I assign an ObservableCollection<BaseViewModel> along with specific DataTemplates...

<c:Wizard Grid.Column="1" ItemsSource="{Binding WizardSteps}">
    <c:Wizard.Resources>
        <DataTemplate DataType="{x:Type vm:WizardStep1ViewModel}">
            <Border Background="Red" />
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:WizardStep2ViewModel}">
            <Border Background="Green" />
        </DataTemplate>
        <DataTemplate DataType="{x:Type vm:WizardStep3ViewModel}">
            <Border Background="Blue" />
        </DataTemplate>
    </c:Wizard.Resources>
</c:Wizard>

...then the DataTemplates are instantiated and shown properly, but no WizardPages are being created in the meantime.

How can I fix it?

Spook
  • 25,318
  • 18
  • 90
  • 167

1 Answers1

0

Your ControlTemplate should include an ItemsPresenter for a WizardPage to get created for each item in the source collection, e.g.:

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="c:Wizard">
            <ItemsPresenter />
        </ControlTemplate>
    </Setter.Value>
</Setter>
mm8
  • 163,881
  • 10
  • 57
  • 88
  • I have one. Items are shown, the problem is that they are `Border`s, not automatically generated `WizardPage`s *containing* the datatemplated `Border`s. – Spook Feb 10 '22 at 15:03
  • Did you put a breakpoint in your `WizardPage`' constructor? It should get hit once for each item in the source collection. – mm8 Feb 10 '22 at 15:05
  • `WizardPage`, I presume? It didn't. The templated items were added without creating the `WizardPage` object. That's the exact problem I'm facing. – Spook Feb 10 '22 at 15:08
  • `ContentPresenter` should be `ItemsPresenter`. I edited this shortly after posting the answer...:) – mm8 Feb 10 '22 at 15:10
  • The problem is that I want to display only one item at a time, so ItemsPresenter doesn't suit me too well :( – Spook Feb 11 '22 at 11:53
  • Then you have misunderstood how an `ItemsControl` and `Selector` works. What does "one item at a time" even mean? You asked how to fix your issue and your question has been answered. – mm8 Feb 11 '22 at 12:56
  • `ComboBox` is a `Selector`, but until dropped down, it displays only one item at a time. Still, every item is wrapped inside ComboBoxItem. I want to achieve similar result. – Spook Feb 11 '22 at 13:21
  • It doesn't really display any items at all until dropped down. It may apply the `ItemTemplate` to the currently selected item but that's another story. What's your point? There are no containers created for you unless there is an `ItemsPresenter`. – mm8 Feb 11 '22 at 13:23
  • I'd like to design a `Wizard` control, which you could use both by manually adding `WizardPage`s and by assigning `ItemsSource`. However, when you assign `ItemsSource`, I need all items to be wrapped inside `WizardPage`s, because these controls contains some important properties I need to bind to (namely, CanFinish, CanCancel, CanNext etc.). Similar behavior can be seen with `ListBox`/`ListBoxItem`, 'TabControl`/`TabItem` etc. However, Wizard shows only one page at a time, so I see not much point in using `ItemsPresenter`. – Spook Feb 11 '22 at 13:27
  • An `ItemsControl` (such as a `ListBox` for example) requires an `ItemsPresenter` to work as expected. – mm8 Feb 11 '22 at 13:28
  • How else are you suppose to generate contains for the items in the `ItemsSource`? – mm8 Feb 11 '22 at 13:29
  • `TabControl` is a `Selector` (so, by extension, also `ItemsControl`), but its default template doesn't contain an `ItemsPresenter`. – Spook Feb 11 '22 at 13:44
  • So you are asking on how to implement a custom `TabControl`? It populates a `TabPanel`. – mm8 Feb 11 '22 at 13:46