Объединение Viewbox, Canvas, ItemsPanelTemplate и ItemsPresenter в пользовательском элементе управления, наследуемом от ItemsPanel

Я хочу продлить ItemsPanel так что я могу отобразить "многоуровневую" визуальную структуру, в которой у меня есть "рамка" с известным размером и множеством наложений, аналогично тому, каким будет приложение для картографии или иллюстрации.

Проблема, которую я имею, состоит в том, чтобы выяснить, как комбинировать вещи, чтобы все работало, как ожидалось. Что я сделал до сих пор:

  • Создан элемент управления, который наследуется от ItemsControl;
  • Внутри управления, положить Viewbox содержащий ItemsPresenter
  • В ресурсах элемента управления создан стиль, нацеленный на его собственный тип, задающий ItemsPanel к шаблону Items, состоящему из Canvas,

Таким образом, я ожидаю, что в Live Tree Inspection я должен увидеть вложенную структуру:

  • LayerContainer (имя класса моего элемента управления)
    • ViewBox
      • ItemsPresenter
      • холст
        • Элемент1
        • Элемент2

Вместо этого я вижу следующее:

  • LayerContainer
    • бордюр
      • ItemsPresenter
      • холст
        • Viewbox
        • Элемент1
        • Элемент2

Таким образом, проблема в том, что ViewBox содержится внутри Canvas, наряду с визуализированными элементами.

Тогда мой вопрос будет таким: как мне структурировать свой элемент управления LayerContainer таким образом, чтобы порядок размещения был ItemsPresenter->Viewbox->Canvas->Items?

Вот мой контроль (имя на самом деле не LayerContainer)

<ItemsControl x:Class="Miotec.PressureMapping.UserControls.BaroLayerContainer"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
            xmlns:local="clr-namespace:Miotec.PressureMapping.UserControls"
            mc:Ignorable="d" 
            d:DesignHeight="450" d:DesignWidth="800">

    <ItemsControl.Resources>
        <Style TargetType="local:BaroLayerContainer">
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <Canvas Width="{Binding Parametros.Colunas}"
                                Height="{Binding Parametros.Linhas}"
                                IsItemsHost="True"/>
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ItemsControl.Resources>

    <Viewbox Stretch="Uniform" x:Name="container">
        <ItemsPresenter
            Width="{Binding ActualWidth, ElementName=container}"
            Height="{Binding ActualHeight, ElementName=container}"/>
    </Viewbox>
</ItemsControl>

2 ответа

Если вам нужен рабочий пример того, как это сделать, то посмотрите мой редактор Perfy на GitHub, соответствующая часть находится в файле MainWindow.xaml. Также показано, как реализовать масштабирование и прокрутку (если вы хотите поддерживать оба, тогда вам на самом деле не нужен ViewBox, только родитель ScrollViewer и LayoutTransform для ItemsControl).

Чтобы ответить на ваш вопрос, каждый элемент обернут в ContentPresenter, трюк состоит в том, чтобы вместо этого установить позицию Canvas для этого родительского элемента. ItemsControl выставляет ItemContainerStyle что позволяет вам сделать именно это:

<ItemsControl.ItemContainerStyle>
    <Style TargetType="{x:Type ContentPresenter}">
        <Setter Property="Canvas.Left" Value="{Binding X}" />
        <Setter Property="Canvas.Top" Value="{Binding Y}" />
    </Style>
</ItemsControl.ItemContainerStyle>

В частности, обратите внимание, что вам не нужно явно объявлять ItemsPresenter сами, это сделано для вас в силу того факта, что вы уже используете ItemsControl для начала. Просто установите свой ItemsPanel на холст, установите стиль ContentPresenter с помощью ItemContainerStyle а затем использовать DataTemplates и / или триггеры для указания внешнего вида самих предметов коллекции.

Подумав и подумав, я понял, что могу "переопределить" Template собственность моего контроля, с ControlTemplate где я бы положил Viewbox как корень и внутри него ItemsPresenter. Затем при рендеринге каждый элемент будет помещен в ItemsPanelTemplate содержащий Canvas, назначенный ItemsPanel имущество.

Ниже приведена окончательная рабочая форма. Принципом решения было установить также Template свойство (для ViewBox), кроме ItemsPanel собственность (для холста).

<ItemsControl x:Class="Miotec.PressureMapping.UserControls.BaroLayerContainer"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
            xmlns:local="clr-namespace:Miotec.PressureMapping.UserControls"
            mc:Ignorable="d" 
            d:DesignHeight="450" d:DesignWidth="800">

    <ItemsControl.Template>
        <ControlTemplate TargetType="ItemsControl">
            <Grid x:Name="container">
                <Viewbox Stretch="Uniform" 
                        Width="{Binding ActualWidth, ElementName=container}"
                        Height="{Binding ActualHeight, ElementName=container}">
                    <ItemsPresenter/>
                </Viewbox>
            </Grid>
        </ControlTemplate>
    </ItemsControl.Template>

    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas Width="{Binding Parametros.Colunas}"
                    Height="{Binding Parametros.Linhas}"
                    IsItemsHost="True"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

</ItemsControl>
Другие вопросы по тегам