Как привязать TabControl к коллекции ViewModels?

В основном у меня в MainViewModel.cs:

ObservableCollection<TabItem> MyTabs { get; private set; }

Однако мне нужно каким-то образом иметь возможность не только создавать вкладки, но и загружать содержимое вкладок и связывать их с соответствующими моделями просмотра при сохранении MVVM.

В принципе, как я могу заставить пользовательский элемент управления загружаться как содержимое элемента вкладки И подключить этот пользовательский элемент управления к соответствующей модели просмотра. Часть, которая делает это трудным, заключается в том, что ViewModel не должен создавать фактические элементы представления, верно? Или может?

В принципе, будет ли это подходящим MVVM:

UserControl address = new AddressControl();
NotificationObject vm = new AddressViewModel();
address.DataContext = vm;
MyTabs[0] = new TabItem()
{
    Content = address;
}

Я спрашиваю только потому, что я создаю View (AddressControl) из ViewModel, что для меня звучит как MVVM «нет-нет».


person michael    schedule 13.04.2011    source источник
comment
+1 хороший вопрос. В руководствах PRISM этот случай не рассматривается.   -  person Markus Hütter    schedule 13.04.2011
comment
Они не рассказали об этом в руководстве, но сделали это в эталонной реализации.   -  person PVitt    schedule 27.04.2011
comment
Это чисто вопрос C # / WPF / MVVM, интегрирована / используется PRISM или нет.   -  person IAbstract    schedule 02.12.2019


Ответы (4)


Это не MVVM. Вы не должны создавать элементы пользовательского интерфейса в своей модели представления.

Вы должны привязать ItemsSource вкладки к вашему ObservableCollection, и он должен содержать модели с информацией о вкладках, которые должны быть созданы.

Вот виртуальная машина и модель, представляющая вкладку:

public sealed class ViewModel
{
    public ObservableCollection<TabItem> Tabs {get;set;}
    public ViewModel()
    {
        Tabs = new ObservableCollection<TabItem>();
        Tabs.Add(new TabItem { Header = "One", Content = "One's content" });
        Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
    }
}
public sealed class TabItem
{
    public string Header { get; set; }
    public string Content { get; set; }
}

А вот как привязки выглядят в окне:

<Window x:Class="WpfApplication12.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <ViewModel
            xmlns="clr-namespace:WpfApplication12" />
    </Window.DataContext>
    <TabControl
        ItemsSource="{Binding Tabs}">
        <TabControl.ItemTemplate>
            <!-- this is the header template-->
            <DataTemplate>
                <TextBlock
                    Text="{Binding Header}" />
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <!-- this is the body of the TabItem template-->
            <DataTemplate>
                <TextBlock
                    Text="{Binding Content}" />
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</Window>

(Обратите внимание: если вы хотите, чтобы на разных вкладках были разные элементы, используйте DataTemplates. Либо модель представления каждой вкладки должна быть отдельным классом, либо создайте собственный DataTemplateSelector, чтобы выбрать правильный шаблон.)

UserControl внутри шаблона данных:

<TabControl
    ItemsSource="{Binding Tabs}">
    <TabControl.ItemTemplate>
        <!-- this is the header template-->
        <DataTemplate>
            <TextBlock
                Text="{Binding Header}" />
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <!-- this is the body of the TabItem template-->
        <DataTemplate>
            <MyUserControl xmlns="clr-namespace:WpfApplication12" />
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>
person Community    schedule 13.04.2011
comment
Что ж, содержимое вкладки - это пользовательский элемент управления, поэтому разве я не буду создавать новый экземпляр пользовательского интерфейса в своей ViewModel? - person michael; 13.04.2011
comment
@michael: в вашем примере вы фактически создаете элемент пользовательского интерфейса в своей ViewModel. В моем примере я создаю модель типа TabItem. В вашем примере TabControl (гипотетически) будет принимать TabItems, созданные вашей ViewModel, и отображать их пользователю. В моем случае он видит свой ItemsSource, создает вкладку для каждой и связывает части каждой вкладки в соответствии с конфигурацией элемента в представлении и типами элементов, которые он отображает. Это главное отличие. Ты понимаешь это? - person ; 13.04.2011
comment
@Will: Что ж, в вашем примере мне еще не пришлось бы делать Tabs.Add(new TabItem { Header = "One", Content = new AddressView() });, который все еще объявляет новый элемент управления пользовательским интерфейсом (AddressView) в ViewModel. Думаю, мне просто трудно понять, как это позволяет избежать создания экземпляра AddressView в ViewModel. - person michael; 13.04.2011
comment
@michael: Нет, не стал бы. В этом магия WPF! Убедитесь сами. Создайте новое приложение WPF под названием WpfApplication12 (извините за имя), замените весь xaml в MainWindow.xaml на xaml в моем ответе и добавьте два класса из моего ответа в ваш проект (в пространстве имен WpfApplication12!). Запустите и посмотрите результаты. - person ; 13.04.2011
comment
@Will: Но в вашем примере вы просто заменяете Content на One Content, тогда как мне нужно, чтобы мой контент был фактическим UserControl. Поэтому, если я возьму ваш пример дословно и хочу, чтобы контент фактически был пользовательским элементом управления, а не некоторым текстом, мне пришлось бы сделать Content = new AddressView(), что не позволяет избежать проблемы создания представления внутри моей модели представления. - person michael; 13.04.2011
comment
@michael: Вы ошибаетесь в предположениях. Отредактировано, чтобы показать, как вы вставляете UserControl. DataContext UserControl будет TabItem. Могут быть выполнены более сложные требования; здесь нет ограничений при правильном использовании MVVM. - person ; 13.04.2011
comment
@ Будет, нет. Я не прошу превратить TabControl в UserControl, но для TabControl для размещения других UserControls, поэтому одна вкладка будет иметь заголовок 1, а содержимое этого TabItem будет UserControl (AddressView). Другой TabItem будет иметь заголовок 2, и содержимое этого TabItem будет другим UserControl (CustomerView). По сути, TabControl будет содержать разные UserControl в качестве содержимого каждого TabItem. Я не верю, что опубликованное решение делает это. - person michael; 14.04.2011
comment
@michael: Нет, но ваши требования совсем не ясны. Если ваши вкладки должны быть динамическими, мое решение работает. Если вам нужны динамические вкладки, содержащие разные пользовательские элементы управления, вы можете привязать ItemsSource к коллекции объектов, каждый из которых имеет свой тип и каждый представляет разные данные, соответствующие каждой вкладке. Затем вы можете создать шаблоны данных (разные для каждого типа ) и селектор по умолчанию выберет соответствующий шаблон при привязке - person ; 14.04.2011
comment
@michael: Если ваши вкладки НЕ должны быть динамическими, вы можете просто определить их в xaml, связать их соответствующим образом и покончить с этим. Итак, что это такое? - person ; 14.04.2011
comment
Потребовалось некоторое время, чтобы пометить это как ответ, но я наконец понял, что вы имели в виду под частью DataTemplates. WPF автоматически подключает представления / модели представления на основе типа модели представления на вкладке, если я определяю шаблон DataTemplate. - person michael; 23.09.2011
comment
Эта статья немного устарела, но я столкнулся с тем же сценарием, что и предсказывал Уилл. Я создаю вкладки динамически для отображения отчетов ssrs. Первая вкладка - это вкладка выбора отчета, и созданные отчеты будут отображаться на последующих вкладках. Я не могу понять, как приведенный выше образец кода связывает Usercontrol в таблице данных tabcontrol в contenttemplate с DataContext. Любая идея? - person Shakti Prakash Singh; 11.04.2012
comment
Что ж, спасибо за ответ. Но меня смущает мой дизайн. У меня есть ViewModel с наблюдаемой коллекцией и представлением, в котором размещен TabControl. Теперь, когда первая вкладка привязана, я выбираю отчет и нажимаю «Создать», как мне добавить этот элемент в наблюдаемую коллекцию. Кажется, я пытаюсь позволить дочернему элементу управления создать родного брата. Я не уверен, возможно ли это, но это мое требование. - person Shakti Prakash Singh; 11.04.2012
comment
Причина, по которой пользователь не может выбирать отчеты из главного окна, заключается в том, что «Отчеты» являются меню из нескольких других. После того, как пользователь выбирает отчет, отображаются несколько списков, и пользователь выбирает из них параметры отчета. Теперь у меня недостаточно места в окне для отображения параметров и элемента управления вкладками в одном окне. - person Shakti Prakash Singh; 11.04.2012
comment
TabItem - это элемент пользовательского интерфейса, если вы спросите меня. Почему это создается в модели представления? - person Gusdor; 05.11.2014
comment
@Gusdor называйте это как хотите. Группа, фу, педантичный комментарий. Независимо от того, что требует ваш дизайн. - person ; 05.11.2014
comment
@ Буду благодарен за то, что я не осуждал использование sealed;) - person Gusdor; 05.11.2014
comment
может кто-нибудь сказать мне, откуда берется MyUserControl? Я не понимаю этой части. - person gts13; 18.04.2017
comment
@ GTS13 - это гипотетический пользовательский элемент управления, который вы бы написали и который предназначен для класса TabItem (или того, что находится в коллекции, привязанной к TabControl.ItemsSource - person ; 18.04.2017
comment
Я действительно не понимаю третьей части вашего кода в вашем ответе. Я не понимаю, что ‹DataTemplate› ‹MyUserControl xmlns = clr-namespace: WpfApplication12 /› ‹/DataTemplate› хранит только один конкретный элемент TabItem элемента управления TabControl. Что делать, если у меня есть другой класс TabItem-UserControl? Скажем, MyUserControl2. Нужно ли мне писать еще один TabControl для размещения MyUserControl2? - person gts13; 19.04.2017
comment
@ Если вы не поняли мой вопрос, у меня здесь то же самое: stackoverflow.com/questions/43410133/ - person gts13; 19.04.2017

В Prism вы обычно делаете вкладку для управления областью, чтобы вам не приходилось брать на себя управление коллекцией связанных страниц вкладок.

<TabControl 
    x:Name="MainRegionHost"
    Regions:RegionManager.RegionName="MainRegion" 
    />

Теперь представления можно добавлять, регистрируясь в регионе MainRegion:

RegionManager.RegisterViewWithRegion( "MainRegion", 
    ( ) => Container.Resolve<IMyViewModel>( ).View );

А здесь вы можете увидеть фирменное блюдо Prism. Представление создается ViewModel. В моем случае я разрешаю ViewModel через контейнер Inversion of Control (например, Unity или MEF). ViewModel получает представление, введенное посредством внедрения конструктора, и устанавливает себя в качестве контекста данных представления.

Альтернативный вариант - зарегистрировать тип представления в контроллере региона:

RegionManager.RegisterViewWithRegion( "MainRegion", typeof( MyView ) );

Использование этого подхода позволяет вам создавать представления позже во время выполнения, например контроллером:

IRegion region = this._regionManager.Regions["MainRegion"];

object mainView = region.GetView( MainViewName );
if ( mainView == null )
{
    var view = _container.ResolveSessionRelatedView<MainView>( );
    region.Add( view, MainViewName );
}

Поскольку вы зарегистрировали тип представления, представление помещается в правильную область.

person PVitt    schedule 14.04.2011

У меня есть конвертер для разделения UI и ViewModel, вот что показано ниже:

<TabControl.ContentTemplate>
    <DataTemplate>
        <ContentPresenter Content="{Binding Tab,Converter={StaticResource TabItemConverter}"/>
    </DataTemplate>
</TabControl.ContentTemplate>

Вкладка - это перечисление в моей модели TabItemViewModel, а TabItemConverter преобразует ее в реальный пользовательский интерфейс.

В TabItemConverter просто получите значение и верните нужный пользовательский элемент управления.

person acai    schedule 15.08.2017

Возможно так:

<UserControl x:Class="Test_002.Views.MainView"
         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:Test_002.Views"
         xmlns:generalView="clr-namespace:Test_002.Views.General"
         xmlns:secVIew="clr-namespace:Test_002.Views.Security"
         xmlns:detailsView="clr-namespace:Test_002.Views.Details"
         mc:Ignorable="d" 
         d:DesignHeight="400" d:DesignWidth="650">
<Grid>
    <DockPanel>
        <StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" Margin="2,5">
            <Button Command="{Binding btnPrev}" Content="Prev"/>
            <Button Command="{Binding btnNext}" Content="Next"/>
            <Button Command="{Binding btnSelected}" Content="Selected"/>
        </StackPanel>
        <TabControl>
            <TabItem Header="General">
                <generalView:GeneralView></generalView:GeneralView>
            </TabItem>
            <TabItem Header="Security">
                <secVIew:SecurityView></secVIew:SecurityView>
            </TabItem>
            <TabItem Header="Details">
                <detailsView:DetailsView></detailsView:DetailsView>
            </TabItem>
        </TabControl>
    </DockPanel>
</Grid>

Think this is the easiest way. Is the MVVM Compatible ?

person BigShady    schedule 21.08.2019