#c# #wpf #binding
#c# #wpf #привязка
Вопрос:
Я пытаюсь создать окно, в котором отображается неизвестное количество страниц внутри на основе коллекции, переданной в builder.
Концепция показалась мне довольно простой — создайте страницу шаблона, создайте коллекцию страниц во время инициализации и отобразите их через ItemControl. Итак:
Класс контекста данных:
class CollectionDetailsWindowContext
{
public List<EntityDetailsPage> Pages { get; }
public CollectionDetailsWindowContext(Collection _collection)
{
Pages = new List<EntityDetailsPage>();
foreach (var entity in _collection.Entities)
Pages.Add(new EntityDetailsPage(entity));
}
}
XAML:
<ItemsControl ItemsSource="{Binding Pages}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Frame Content="{Binding .}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Если я запускаю этот код, это приводит к следующему исключению
System.InvalidOperationException: 'Page can have only Window or Frame as parent.'
Когда я пытаюсь выполнить все привязки вручную без ItemControl (таким образом, выбирая первые несколько объектов из коллекции и привязывая их к фреймам), все работает нормально, что наводит меня на мысль, что проблема в том, как ItemControl передает страницу во фрейм.
Ответ №1:
Хорошо, итак, вот пара вещей.
Во-первых, традиционный подход заключается в разделении данных и представления. Если вы загуглите «MVVM» или «MVC», вы увидите массу информации об этом. Лично я рекомендую MVVM. Таким образом, у вас обычно не было бы страницы, которая привязывается к другим страницам. Отображаемая страница («представление») должна привязываться к данным (ну, к «модели представления», представляющей данные), а не к другим страницам.
Во-вторых, страницы обычно отображаются во фреймах с использованием NavigateTo. Вы могли бы создать StackPanel, содержащую фреймы, а затем загружать страницу в каждый фрейм, но это кажется излишеством. Было бы проще просто иметь ListView и шаблон для деталей. Если у вас есть несколько типов сведений, вы могли бы использовать DataTemplateSelector в представлении списка.
В-третьих, в конечном итоге вы, вероятно, захотите обновить данные, для чего вам понадобится коллекция, которая уведомляет представление об изменениях в выбранном списке сведений об объектах. Это модель представления для списка. Также каждая сущность (EntityDetail в вашем случае) должна уведомлять о своих свойствах в потоке пользовательского интерфейса (или иметь отдельный объект модели представления, который это делает), но я не буду слишком углубляться в это здесь. Опять же, я говорю об обычном способе выполнения чего-либо, конечно, есть много других способов.
Итак … возможно, что-то вроде этого:
partial class DetailsPage {
public ObservableCollection<EntityDetail> EntityDetails {get;}
public DetailsPage( ObservableCollection<EntityDetail> entityDetails) {
this.EntityDetails = entityDetails;
}
}
<Page
x:Class="DetailsPage"
…
>
<Page.Resources>
<ResourceDictionary>
<DataTemplate x:Name="EntityDetailDataTemplate" x:DataType="local:EntityDetail">
<TextBlock Text={x:Bind Name, Mode=OneWay}/> (assuming Name is a property in EntityDetail)
</DataTemplate>
</ResourceDictionary>
</Page.Resources>
<ListView
ItemsSource={x:Bind EntityDetails, Mode=OneWay}
ItemTemplate={StaticResource EntityDetailDataTemplate}
…
>
</ListView>
</Page>
Комментарии:
1. Спасибо, вы правы относительно подхода MVVM, и в конце дня я, вероятно, собираюсь переключиться на него. На данный момент, однако, моя основная проблема с использованием DataTemplates заключается в том, что страница для EntityDetails довольно сложная (1800-2000 строк xaml), и изменять ее без конструктора для наблюдения немедленных последствий изменений довольно хлопотно. Я хотел бы принять как ваш, так и mm8 ответ.
2. Существуют разные взгляды на то, что следует делать, когда что-то «немного перепутано» в чьем-то коде. Лично, если это существенное изменение, я предпочитаю исправить это заранее, а не отпускать. В противном случае я просто в конечном итоге копаю дыру глубже.
3. @JagdCrab, еще одна мысль. Если вы действительно хотите привязать к списку страниц, вы могли бы управлять из Frame, добавить свойство страницы и переходить на страницу всякий раз, когда она меняется. Тогда, по вашему мнению, вы могли бы использовать ListView и использовать этот класс, производный от фрейма, в DataTemplate.
Ответ №2:
…что наводит меня на мысль, что проблема заключается в том, как ItemControl передает страницу во фрейм.
Причина, по которой ваш код не работает, заключается в том, что ItemTemplate
не применяется к UIElements
таким Pages
как.
Вы могли бы исправить это, создав свой собственный ItemsControl
и переопределив IsItemItsOwnContainerOverride
метод:
public class FrameItemsControl : ItemsControl
{
protected override bool IsItemItsOwnContainerOverride(object item)
{
return false;
}
}
Возвращается реализация по умолчанию (item is UIElement)
.
Использование:
<local:FrameItemsControl ItemsSource="{Binding Pages}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Frame Content="{Binding .}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</local:FrameItemsControl>
Комментарии:
1. @JagdCrab: Вы попробовали мое предложение?