powered by simpleCommunicator - 2.0.49     © 2025 Programmizd 02
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / TabControl, ContentTemplate, и DataGrid внутри DataTemplate - как отключить cинхронизацию
5 сообщений из 5, страница 1 из 1
TabControl, ContentTemplate, и DataGrid внутри DataTemplate - как отключить cинхронизацию
    #39742447
WinterGraveyard
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Имеется вот такое окошко:
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
public partial class MainWindow
{
  public MainWindow()
  {
    InitializeComponent();
    Items = new List<List<TestItem>>
    {
      new List<TestItem>{new TestItem(1), new TestItem(2)},
      new List<TestItem>{new TestItem(3), new TestItem(4)}
    };
    DataContext = this;
  }

  public IReadOnlyCollection<IReadOnlyCollection<TestItem>> Items { get; }
}

public class TestItem
{
  public int Id { get; }
  public string Name { get; }
  public DateTime Date { get; }

  public TestItem(int id)
  {
    Id = id;
    Name = ((char) (64 + id)).ToString();
    Date = DateTime.Now.AddYears(id);
  }
}

public class VisibilityToBoolConverter : IValueConverter
{
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return (Visibility)value == Visibility.Visible;
  }

  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return value != null && (bool)value ? Visibility.Visible : Visibility.Collapsed;
  }
}


и вот такой XAML:
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
<Window
  ....................
  Height="700"
  Width="700">


  <Window.Resources>
    <local:VisibilityToBoolConverter x:Key="VisibilityToBoolConverter" />
    <DataTemplate x:Key="DataTemplate">
      <Border BorderThickness="1" BorderBrush="Black" Margin="3">
        <Grid>
          <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
          </Grid.RowDefinitions>
          <DataGrid
            Margin="3"
            x:Name="DataGrid"
            IsReadOnly="True"
            AutoGenerateColumns="False"
            ItemsSource="{Binding}">
            <DataGrid.Columns>
              <DataGridTextColumn
                Binding="{Binding Id, Mode=OneWay}"
                Header="Id"
                Width="Auto" />
              <DataGridTextColumn
                Binding="{Binding Name, Mode=OneWay}"
                Header="Name"
                Width="Auto" />
              <DataGridTextColumn
                Binding="{Binding Date, Mode=OneWay}"
                Header="Date"
                Width="Auto" />
            </DataGrid.Columns>
          </DataGrid>
          <ListBox
            Grid.Row="1"
            Margin="3"
            ItemsSource="{Binding Columns, ElementName=DataGrid}">
            <ItemsControl.ItemsPanel>
              <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal" />
              </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemContainerStyle>
              <Style TargetType="{x:Type ListBoxItem}">
                <Setter Property="OverridesDefaultStyle" Value="True" />
                <Setter Property="Template">
                  <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                      <CheckBox
                        Margin="3"
                        IsChecked="{Binding Visibility, Converter={StaticResource VisibilityToBoolConverter}, Mode=TwoWay}"
                        Content="{Binding Header}" />
                    </ControlTemplate>
                  </Setter.Value>
                </Setter>
              </Style>
            </ItemsControl.ItemContainerStyle>
          </ListBox>
        </Grid>
      </Border>
    </DataTemplate>
  </Window.Resources>

  <TabControl ItemsSource="{Binding Items}" ContentTemplate="{StaticResource DataTemplate}"/>
</Window>


Проблема в том, что состояние DataGrid синхронизируется между вкладками - меняем размер колонки на одной вкладке, переключаемся на другую - он там тоже изменился. Скрываем колонку у одного грида (чекбоксами под гридом) - колонка скрывается у грида на второй вкладке. Причем если TabControl заменить на любой другой ItemsControl - на ListBox, например, то этой синхронизации не наблюдается.
Есть подозрение, что это вызвано тем, что при смене вкладки TabControl делает перегенерацию контента для новой вкладки. Это можно как-то отключить? Если нет, то как вообще можно побороть такое поведение?
...
Рейтинг: 0 / 0
TabControl, ContentTemplate, и DataGrid внутри DataTemplate - как отключить cинхронизацию
    #39742459
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
<Window x:Class="WpfApp23.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp23"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TabControl>
            <TabControl.Items>
                <TabItem Header="Tab 1">
                    <Grid> <!-- Седержимое --> </Grid>
                </TabItem>
                <TabItem Header="Tab 2">
                    <Grid> <!-- Седержимое --> </Grid>
                </TabItem>
            </TabControl.Items>
        </TabControl>
    </Grid>
</Window>


Это связано с тем, как работает ContentPresenter, если в качестве Content выступает UIElement, то в качестве содержимого будет отображаться сам элемент. Но 1 элемент не может отображаться одновременно в 2 ContentPresenter'ах.
Если в качестве содержимого (Content) будет выступа не UIElement, а обычный объект или модель представления, то будет использован либо шаблон по умолчанию (TextBlock) или заданный в ContentTemplate или полученный из ContentTemplateSelector.
Так как при переключении вкладок переключает содержимое ContentPresenter'а у TabControl и это не UIElement, то на основе шаблона содержимое формируется каждый раз при переключении. Это может вызывать такие проблемы, как у вас, не только дублирование состояния, но и потерю состояния, которые не хранятся в модели представления (фокус и прочие состояния представления).
Если количество вкладок не предопределено заранее, то пример выше не выход. Придется доработать элемент управления TabControl, нужно создать ContentControl для каждого TabItem'а, поместить туда содержимое вкладки, задать шаблон и сохранить ссылку на этот объект.
При переключении вкладок в качество содержимого должны подставляться ContentControl'ы сохраненные ранее. Так как содержимое сохраненных элементов сформировано, оно не будет перестраиваться.
...
Рейтинг: 0 / 0
TabControl, ContentTemplate, и DataGrid внутри DataTemplate - как отключить cинхронизацию
    #39742491
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вот реализация через мультконвертер. Топорная, это пример на коленке, но рабочий.
Для работы нужно поменять шаблон TabControl'а.

Код: xml
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
<Window x:Class="WpfApp23.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp23"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainModel/>
    </Window.DataContext>
    <Window.Resources>

        <local:TabItemConverter x:Key="TabItemConverter"/>

        <DataTemplate x:Key="TabTemplate" DataType="{x:Type local:TabModel}">
            <ListBox Margin="5" ItemsSource="{Binding Items}"/>
        </DataTemplate>

        <DataTemplate x:Key="HeaderTemplate" DataType="{x:Type local:TabModel}">
            <TextBlock Text="{Binding Name}"/>
        </DataTemplate>
        
        <SolidColorBrush x:Key="TabItem.Selected.Background" Color="#FFFFFF"/>
        <SolidColorBrush x:Key="TabItem.Selected.Border" Color="#ACACAC"/>
        
        <Style x:Key="TabControlStyle" TargetType="{x:Type TabControl}" BasedOn="{StaticResource {x:Type TabControl}}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TabControl}">
                        <Grid x:Name="templateRoot" ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition x:Name="ColumnDefinition0"/>
                                <ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
                            </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition x:Name="RowDefinition0" Height="Auto"/>
                                <RowDefinition x:Name="RowDefinition1" Height="*"/>
                            </Grid.RowDefinitions>
                            <TabPanel x:Name="headerPanel" Background="Transparent" Grid.Column="0" IsItemsHost="true" Margin="2,2,2,0" Grid.Row="0" KeyboardNavigation.TabIndex="1" Panel.ZIndex="1"/>
                            <Border x:Name="contentPanel" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="0" KeyboardNavigation.DirectionalNavigation="Contained" Grid.Row="1" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local">
                                
                                <ContentPresenter x:Name="PART_SelectedContentHost" 
                                                  Margin="{TemplateBinding Padding}"
                                                  SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                                    <ContentPresenter.Content>
                                        <MultiBinding Converter="{StaticResource TabItemConverter}">
                                            <Binding Path="SelectedIndex" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=TabControl}"/>
                                            <Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=TabControl}"/>
                                        </MultiBinding>
                                    </ContentPresenter.Content>
                                </ContentPresenter>
                                
                            </Border>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="TabStripPlacement" Value="Bottom">
                                <Setter Property="Grid.Row" TargetName="headerPanel" Value="1"/>
                                <Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
                                <Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
                                <Setter Property="Height" TargetName="RowDefinition1" Value="Auto"/>
                                <Setter Property="Margin" TargetName="headerPanel" Value="2,0,2,2"/>
                            </Trigger>
                            <Trigger Property="TabStripPlacement" Value="Left">
                                <Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/>
                                <Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
                                <Setter Property="Grid.Column" TargetName="headerPanel" Value="0"/>
                                <Setter Property="Grid.Column" TargetName="contentPanel" Value="1"/>
                                <Setter Property="Width" TargetName="ColumnDefinition0" Value="Auto"/>
                                <Setter Property="Width" TargetName="ColumnDefinition1" Value="*"/>
                                <Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
                                <Setter Property="Height" TargetName="RowDefinition1" Value="0"/>
                                <Setter Property="Margin" TargetName="headerPanel" Value="2,2,0,2"/>
                            </Trigger>
                            <Trigger Property="TabStripPlacement" Value="Right">
                                <Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/>
                                <Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
                                <Setter Property="Grid.Column" TargetName="headerPanel" Value="1"/>
                                <Setter Property="Grid.Column" TargetName="contentPanel" Value="0"/>
                                <Setter Property="Width" TargetName="ColumnDefinition0" Value="*"/>
                                <Setter Property="Width" TargetName="ColumnDefinition1" Value="Auto"/>
                                <Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
                                <Setter Property="Height" TargetName="RowDefinition1" Value="0"/>
                                <Setter Property="Margin" TargetName="headerPanel" Value="0,2,2,2"/>
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="false">
                                <Setter Property="TextElement.Foreground" TargetName="templateRoot" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        
    </Window.Resources>
    <Grid>
        <TabControl ItemsSource="{Binding Tabs}"
                    ContentTemplate="{StaticResource TabTemplate}"
                    ItemTemplate="{StaticResource HeaderTemplate}" Style="{DynamicResource TabControlStyle}"/>
    </Grid>
</Window>



Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
public class TabItemConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values.Length == 2 && values[0] is int selectedIndex && values[1] is TabControl tabControl)
        {
            var tabItem = tabControl.ItemContainerGenerator.ContainerFromIndex(selectedIndex) as TabItem;
            if (tabItem != null)
            {
                if (tabItem.Tag == null)
                {
                    var control = new ContentControl { Content = tabItem.Content, ContentTemplate = tabControl.ContentTemplate };
                    tabItem.Tag = control;
                }
                return tabItem.Tag;
            }
        }
        return null;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}


модель для примера и роли не играет
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
public class TabModel
{
    public TabModel(string name)
    {
        Name = name;
        var rnd = new Random();
        var count = rnd.Next(100, 500);
        Items = new List<string>(count);
        for (var i = 0; i < count; i++)
            Items.Add($"{Name} - Item #{i}");
    }

    public string Name { get; }
    public List<string> Items { set; get; }
}

public class MainModel
{
    public MainModel()
    {
        Tabs = new List<TabModel>(5)
        {
            new TabModel("Tab1"),
            new TabModel("Tab2"),
            new TabModel("Tab3"),
            new TabModel("Tab4"),
            new TabModel("Tab5"),
        };
    }

    public List<TabModel> Tabs { set; get; }
}
...
Рейтинг: 0 / 0
TabControl, ContentTemplate, и DataGrid внутри DataTemplate - как отключить cинхронизацию
    #39742655
WinterGraveyard
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Роман, спасибо.
Погуглил - оказывается, проблема старая, и тянется чуть ли не с 2007-го года. Ситуация в точности противоположна моему предположению: для сгенерированных TabItems по ContentTemplate TabControl не пересоздает контент, а только меняет data item из ItemsSource, и всё то, что не связано с VM, остается одинаковым для всех вкладок (как вы писали выше).
Нашел несколько решений:
https://stackoverflow.com/questions/9794151/stop-tabcontrol-from-recreating-its-children
https://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization
- остановлюсь, наверное, на последнем (оно вполне работает, я проверил).
...
Рейтинг: 0 / 0
TabControl, ContentTemplate, и DataGrid внутри DataTemplate - как отключить cинхронизацию
    #39742832
Roman Mejtes
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
WinterGraveyardРоман, спасибо.
Погуглил - оказывается, проблема старая, и тянется чуть ли не с 2007-го года. Ситуация в точности противоположна моему предположению: для сгенерированных TabItems по ContentTemplate TabControl не пересоздает контент, а только меняет data item из ItemsSource, и всё то, что не связано с VM, остается одинаковым для всех вкладок (как вы писали выше).
Нашел несколько решений:
https://stackoverflow.com/questions/9794151/stop-tabcontrol-from-recreating-its-children
https://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization
- остановлюсь, наверное, на последнем (оно вполне работает, я проверил).
разберитесь как работает ContentPresenter, что и как он отображает и тогда вопросов таких не будет.
подобной "магии" в WPF полно, и когда не знаешь, как это работает, очень тяжко. Магия WPF это палка о двух концах.
...
Рейтинг: 0 / 0
5 сообщений из 5, страница 1 из 1
Форумы / WPF, Silverlight [игнор отключен] [закрыт для гостей] / TabControl, ContentTemplate, и DataGrid внутри DataTemplate - как отключить cинхронизацию
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]