Вкладка WPF, сохраняющая контекст только на первой вкладке - PullRequest
0 голосов
/ 05 августа 2020

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

У меня есть главное окно с тремя пользовательскими элементами управления:

  1. меню, которое направляет содержимое панели выбора
  2. панель выбора, которая создает вкладки в рабочей области
  3. рабочая область - элемент управления вкладками.

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

Каждая вкладка состоит из нескольких собственных подмоделей, представляющих сетки, составленные из модели данных продукта - у продукта есть несколько материалов (SKU). У каждого SKU есть BillOfMaterials, и т. Д. c.

Я выяснил, где, но не почему, возникает проблема.

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

ItemTabVM имеет в себе множество других виртуальных машин. Вот код в виртуальной машине рабочей области:

...
        public _WorkAreaVM()
        {
            Mediator.Subscribe("newtab_item", LoadNewItemTab);
        }
        private void LoadNewItemTab(object obj)
        {
            String id = AppData.SelectedPanelValue;

            if (IsTabUnique(id) == true)
                TabList.Add(ItemTabVM(id));

            SetSelectedTab(id);
        }
...

XAML для рабочей области

...

    <UserControl x:Class="CWApp.Views._WorkAreaUC"
                 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:CWApp.Views"
                 xmlns:vm="clr-namespace:CWApp.ViewModels"
                 mc:Ignorable="d">
        <UserControl.DataContext>
            <vm:_WorkAreaVM/>
        </UserControl.DataContext>
    
        <DockPanel>
            <Label Content="WorkArea" />
            <TabControl
                ItemsSource="{Binding TabList}"
                SelectedIndex="{Binding SelectedTabIndex}" >
    
                <TabControl.Resources>
                    <DataTemplate DataType="{x:Type vm:ItemTabVM}">
                        <local:ItemUC/>
                    </DataTemplate>
                    <DataTemplate DataType="{x:Type vm:VendorTabVM}">
                        <local:VendorTabUC/>
                    </DataTemplate>
                </TabControl.Resources>
    
                <TabControl.ItemTemplate>
                    <DataTemplate DataType="{x:Type vm:ITabViewModel}">
                        <TextBlock>
                            <Run Text="{Binding Header}"/>
                            <Hyperlink Command="{Binding CloseCommand}">X</Hyperlink>
                        </TextBlock>
                    </DataTemplate>
                </TabControl.ItemTemplate>
                
            </TabControl>
        </DockPanel>
    </UserControl>

...

XAML для каждого ItemTabU C

...
    
    <UserControl x:Class="CWApp.Views.ItemTabUC "
             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:vm="clr-namespace:CWApp.ViewModels"
             xmlns:local1="clr-namespace:CWApp.Helpers"
             xmlns:local="clr-namespace:CWApp.Views"
             
             mc:Ignorable="d" >
        <StackPanel>
            <StackPanel.Resources>
                <DataTemplate DataType="{x:Type vm:ProductMainVM}">
                    <local:ProductMainUC/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type vm:MaterialGridVM}">
                    <local:MaterialGridUC/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type vm:BOMGridVM}">
                    <local:BOMGridUC/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type vm:RouterGridVM}">
                    <local:RouterGridUC/>
                </DataTemplate>
                <DataTemplate DataType="{x:Type vm:VendorGridVM}">
                    <local:VendorGridUC/>
                </DataTemplate>
            </StackPanel.Resources>
            <ContentControl Content="{Binding ProductMain}" />
            <ContentControl Content="{Binding MaterialGrid}" />
            <ContentControl Content="{Binding BOMGrid}" />
            <ContentControl Content="{Binding RouterGrid}" />
            <ContentControl Content="{Binding VendorGrid}" />
        </StackPanel>
    </UserControl>
    
...

Один из виртуальная машина в ItemTabVM - это MaterialVM (таблица данных SKU). Когда пользователь выбирает SKU, он должен изменить BillOfMaterialVM (DataTable of Components) на основе выбранного SKU.

MaterialU C DataGrid SelectedItem привязывается к VM ...

        <DataGrid Grid.Row="1" Name="dgMaterials" 
              ItemsSource="{Binding MaterialTable}"
              SelectedItem="{Binding SelectedRow}"
              AutoGenerateColumns="false">

. ..

Когда MaterialVM изменяется, он сообщает ItemTabVM, что произошло изменение. Это обрабатывается с помощью элемента управления SelectedItem datagrid, который, в свою очередь, запускает опосредованное событие.

...

    public Material SelectedMaterial
    {
        get { return _selectedMaterial; }
        set
        {
            _selectedMaterial = value;
            OnPropertyChanged("SelectedMaterial");
            NotifyMaterialChanged();
        }
    }

    private void NotifyMaterialChanged()
    {
        Mediator.Notify("materialChange", true);
    }

...

ItemTabVM (предок MaterialVM и BillOfMaterialVM) подписан на изменения в MaterialVM.SelectedMaterial, и при обнаружении сообщает BOMVM, какую точку данных загружать.

...

    private void CreateSubscriptions()
    {
       Mediator.Subscribe("materialChange", SetMaterialChanged);
    }
    private void SetMaterialChanged(object obj)
    {
        String n = ThisProduct.ProductID; //error discovery
        if(MaterialGrid.SelectedMaterial != null)
            BOMGrid.SetGrid(MaterialGrid.SelectedMaterial);
    }

...

Строка с пометкой «обнаружение ошибок» использует ProductID, загруженный в ItemTabVM, чтобы показать, какая вкладка элементов фактически активируется. Независимо от того, из какого MaterialVM был отправлен триггер, всегда срабатывала подписка Tab1 ItemTabVM.

Почему подписки Tab не остаются локальными в контексте их дочерних элементов?

1 Ответ

0 голосов
/ 06 августа 2020

Ваше приложение logi c действительно сложно проследить за вашими объяснениями. Я имею в виду, что вы хорошо старались, но для меня, который не знает, что происходит, это крепкий орешек. Возможно, вы могли бы показать логику / взаимодействие модели представления, о котором вы говорите.

Сначала вы должны понять, что TabControl - это гибридный элемент управления, являющийся ItemsControl и ContentControl. Часть ItemsControl обрабатывает TabItems, которые отображаются внутри TabPanel. Эти элементы являются заголовками вкладок. Часть ContentControl обрабатывает фактическое содержимое выбранного TabItem. Это означает, что все заголовки вкладок имеют один и тот же ContentPresenter.

При переключении между вкладками, т.е. при выборе следующего TabItem содержимое заменяется. Дело в том, что при замене предыдущего контента он теряет свое состояние. Ответственность за сохранение данных перед переключением вкладки лежит на разработчике. Это часто приводит к неожиданным эффектам, поскольку большинство людей считает, что каждая вкладка имеет собственное связанное содержимое.

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

Создайте модель основного представления, которая является DataContext из TabControl. Эта модель представления предоставляет источник данных для TabControl, но также предоставляет logi c для управления обработкой вкладок. Отдельные модели представления вкладок не должны заботиться о других вкладках. Это основная модель представления, которая должна наблюдать за каждым элементом данных и обрабатывать события, например, инициализируя связанные элементы данных или показывая / выбирая следующую вкладку, выбирая соответствующий элемент данных.

Из ваших описаний кажется, что ваша модель представления прислушивается к просмотру событий, чтобы знать, когда менять вкладки. Это не верно. Ваша модель представления или, в данном случае, модель основного представления должна отображать ICommand. Этот ICommand, когда выполняется Button в представлении, устанавливает, например, свойство ViewModel.SelectedTab, которое связывается со свойством TabControl.SelectedItem. Когда вы изменяете выбранный TabItem, устанавливая свойство SelectedItem, вы можете подписаться на события элементов.

Основная модель представления в этом сценарии - единственная модель представления, которая знает о других моделях представления (вкладка Предметы). Эта структура должна решить вашу проблему.

...