Невозможно изменить вложенное свойство VirtualizationMode в ItemsControl после вызова Measure на панели ItemsHost - PullRequest
1 голос
/ 07 ноября 2019

Когда я пытаюсь установить VirtualizationMode на моем ListView на Recycling, я получаю ошибку из заголовка:

Невозможно изменить вложенное свойство VirtualizationMode для ItemsControl после вызова Measureна панели ItemsHost.

Я пытаюсь установить присоединенное свойство программно, но при попытке определить VirtualizationMode в конструкторе XAML выдает ту же ошибку из заголовка. У кого-нибудь были подобные проблемы?

Мой взгляд на XAML:

<Window x:Class="FinalVirtualizationApp.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:FinalVirtualizationApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="900" Width="800"
        xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
         TextElement.Foreground="{DynamicResource MaterialDesignBody}"
         TextElement.FontWeight="Regular"
         TextElement.FontSize="13"
         TextOptions.TextFormattingMode="Ideal"
         TextOptions.TextRenderingMode="Auto"
         Background="{DynamicResource MaterialDesignPaper}"
         FontFamily="{DynamicResource MaterialDesignFont}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
        </Grid.ColumnDefinitions>

        <GroupBox Grid.Row="0" Header="UI virtualization options">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
                <CheckBox Content="UI virtualization" VerticalAlignment="Center" IsChecked="{Binding IsUIVirtualization}"/>
                <Grid Grid.Column="1">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition/>
                    </Grid.ColumnDefinitions>
                    <Label Content="Container Recycling:" VerticalAlignment="Center"/>
                    <ComboBox Grid.Column="1" VerticalAlignment="Center" SelectedValue="{Binding ContainerRecyclingType}" SelectedValuePath="Content">
                        <ComboBoxItem>Recycling</ComboBoxItem>
                        <ComboBoxItem>Standard</ComboBoxItem>
                    </ComboBox>
                </Grid>
                <CheckBox Content="Deferred scrolling" Grid.Row="1" VerticalAlignment="Center" IsChecked="{Binding IsDeferredScrolling}">

                </CheckBox>
                <Grid Grid.Row="1" Grid.Column="1">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="74*"/>
                        <ColumnDefinition Width="253*"/>
                    </Grid.ColumnDefinitions>
                    <Label Content="Scroll unit" VerticalAlignment="Center" Margin="0,0,0,1"/>
                    <ComboBox Grid.Column="1" VerticalAlignment="Center" SelectedValue="{Binding ScrollUnitType}" SelectedValuePath="Content" Grid.ColumnSpan="2" Margin="0,2,0,3">
                        <ComboBoxItem>Item</ComboBoxItem>
                        <ComboBoxItem>Pixel</ComboBoxItem>
                    </ComboBox>
                </Grid>
            </Grid>
        </GroupBox>
        <GroupBox Grid.Row="1"  Header="Data virtualization">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <GroupBox Header="ItemsProvider">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <StackPanel Orientation="Horizontal">
                            <Label Content="Number of Items: "/>
                            <TextBox Width="60" Text="{Binding NumberOfItems}"/>
                        </StackPanel>
                        <StackPanel Grid.Column="1" Orientation="Horizontal">
                            <Label Content="Fetch delay(ms): "/>
                            <TextBox Width="60" Text="{Binding FetchDelay}"/>
                        </StackPanel>
                    </Grid>
                </GroupBox>
                <GroupBox Grid.Row="1" Header="Collection">
                    <StackPanel>
                        <StackPanel Orientation="Horizontal" Margin="0,2,0,0">
                            <TextBlock Text="Type:" Margin="5" TextAlignment="Right" VerticalAlignment="Center"/>
                            <RadioButton x:Name="rbNormal" GroupName="rbGroup" Margin="5" Content="List(T)" VerticalAlignment="Center" Command="{Binding CollectionTypeChangeCommand}" CommandParameter="List"/>
                            <RadioButton x:Name="rbVirtualizing" GroupName="rbGroup" Margin="5" Content="VirtualizingList(T)" VerticalAlignment="Center" Command="{Binding CollectionTypeChangeCommand}" CommandParameter="VirtualizingList"/>
                            <RadioButton x:Name="rbAsync" GroupName="rbGroup" Margin="5" Content="AsyncVirtualizingList(T)"  VerticalAlignment="Center" Command="{Binding CollectionTypeChangeCommand}" CommandParameter="AsyncVirtualizingList"/>
                        </StackPanel>
                        <StackPanel Orientation="Horizontal" Margin="0,2,0,0">
                            <TextBlock Text="Page size:" Margin="5" TextAlignment="Right" VerticalAlignment="Center"/>
                            <TextBox x:Name="tbPageSize" Margin="5" Text="{Binding PageSize}" Width="60" VerticalAlignment="Center"/>
                            <TextBlock Text="Page timeout (s):" Margin="5" TextAlignment="Right" VerticalAlignment="Center"/>
                            <TextBox x:Name="tbPageTimeout" Margin="5" Text="{Binding PageTimeout}" Width="60" VerticalAlignment="Center"/>
                        </StackPanel>
                    </StackPanel>
                </GroupBox>
            </Grid>
        </GroupBox>
        <StackPanel Orientation="Horizontal" Grid.Row="2">
            <TextBlock Text="Memory Usage:" Margin="5" VerticalAlignment="Center"/>
            <TextBlock x:Name="tbMemory" Margin="5" Width="80" Text="{Binding MemoryUsage}" VerticalAlignment="Center"/>

            <Button Content="Refresh" Margin="5" Width="100" VerticalAlignment="Center" Command="{Binding RefreshCommand}"/>
        </StackPanel>
        <ListView Grid.Row="3" Name="lvItems">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Expander Header="{Binding DeviceName}">
                        <StackPanel>

                        </StackPanel>
                    </Expander>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Window>

И я пытаюсь установить здесь VirtualizationMode:

public void SetUIVirtualizationOptions()
{
    listView.SetValue(VirtualizingStackPanel.IsVirtualizingProperty, IsUIVirtualization);
    listView.SetValue(ScrollViewer.IsDeferredScrollingEnabledProperty, IsDeferredScrolling);

    if(ContainerRecyclingType == "Recycling")
        listView.SetValue(VirtualizingStackPanel.VirtualizationModeProperty, VirtualizationMode.Recycling);
    else
        listView.SetValue(VirtualizingStackPanel.VirtualizationModeProperty, VirtualizationMode.Standard);


    if (ScrollUnitType == "Item")
        listView.SetValue(VirtualizingPanel.ScrollUnitProperty, ScrollUnit.Item);
    else
        listView.SetValue(VirtualizingPanel.ScrollUnitProperty, ScrollUnit.Pixel);
}

Редактировать:Код моего окна позади, где я передаю представление списка в viewmodel:

    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel(lvItems);
        }
    }

И часть кода viewmodel (за исключением методов получения и установки для уменьшения занимаемого места):

public class MainWindowViewModel : BindableBase
    {
        #region private fields
        private ListView listView;
        private bool isUIVirtualization;
        private bool isDeferredScrolling;
        private string containerRecyclingType;
        private string scrollUnitType;
        private int numberOfItems;
        private int fetchDelay;
        private string collectionType;
        private int pageSize;
        private int pageTimeout;
        private string memoryUsage;
        private DemoItemProvider itemsProvider;
        #endregion

        #region commands
        public RelayCommand<string> CollectionTypeChangeCommand { get; set; }
        public RelayCommand RefreshCommand { get; set; }
        public RelayCommand<string> ContainerRecyclingTypeChangeCommand { get; set; }
        public RelayCommand<string> ScrollUnitTypeChangeCommand { get; set; }
        #endregion

        public MainWindowViewModel(ListView lvItems)
        {
            this.listView = lvItems;
            PageSize = 100;
            PageTimeout = 30;
            NumberOfItems = 1000000;
            FetchDelay = 1000;

            CollectionTypeChangeCommand = new RelayCommand<string>(CollectionTypeChangeFunc);
            RefreshCommand = new RelayCommand(RefreshFunc);
            ContainerRecyclingTypeChangeCommand = new RelayCommand<string>(ContainerRecyclingTypeChangeFunc);
            ScrollUnitTypeChangeCommand = new RelayCommand<string>(ScrollUnitTypeChangeFunc);

            // use a timer to periodically update the memory usage
            DispatcherTimer timer = new DispatcherTimer();
            timer.Interval = new TimeSpan(0, 0, 1);
            timer.Tick += timer_Tick;
            timer.Start();
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            MemoryUsage = string.Format("{0:0.00} MB", GC.GetTotalMemory(true) / 1024.0 / 1024.0);
        }

        #region command methods
        public void CollectionTypeChangeFunc(string type)
        {
            CollectionType = type;
        }

        public void RefreshFunc()
        {
            SetUIVirtualizationOptions();
            itemsProvider = new DemoItemProvider(NumberOfItems, FetchDelay);


            if (collectionType == "List")
            {
                listView.ItemsSource = new List<DataItem>(itemsProvider.FetchRange(0, itemsProvider.FetchCount()));
            }
            else if (collectionType == "VirtualizingList")
            {
                listView.ItemsSource = new VirtualizingCollection<DataItem>(itemsProvider, pageSize);
            }
            else if (collectionType == "AsyncVirtualizingList")
            {
                listView.ItemsSource = new AsyncVirtualizingCollection<DataItem>(itemsProvider, pageSize, pageTimeout * 1000);
            }
        }

        public void ContainerRecyclingTypeChangeFunc(string type)
        {
            ContainerRecyclingType = type;
        }

        public void ScrollUnitTypeChangeFunc(string type)
        {
            ScrollUnitType = type;
        }

        public void SetUIVirtualizationOptions()
        {
            listView.SetValue(VirtualizingStackPanel.IsVirtualizingProperty, IsUIVirtualization);
            listView.SetValue(ScrollViewer.IsDeferredScrollingEnabledProperty, IsDeferredScrolling);

            if(ContainerRecyclingType == "Recycling")
                listView.SetValue(VirtualizingStackPanel.VirtualizationModeProperty, VirtualizationMode.Recycling);
            else
                listView.SetValue(VirtualizingStackPanel.VirtualizationModeProperty, VirtualizationMode.Standard);


            if (ScrollUnitType == "Item")
                listView.SetValue(VirtualizingPanel.ScrollUnitProperty, ScrollUnit.Item);
            else
                listView.SetValue(VirtualizingPanel.ScrollUnitProperty, ScrollUnit.Pixel);
        }
        #endregion
    }

Ответы [ 2 ]

1 голос
/ 07 ноября 2019

Источник кода для VirtualizingStackPanel указывает, что это свойство можно установить только до инициализации панели:

/// <summary>
///     Attached property for use on the ItemsControl that is the host for the items being 
///     presented by this panel. Use this property to modify the virtualization mode.
/// 
///     Note that this property can only be set before the panel has been initialized 
/// </summary>
public static readonly DependencyProperty VirtualizationModeProperty = 
    DependencyProperty.RegisterAttached("VirtualizationMode", typeof(VirtualizationMode), typeof(VirtualizingStackPanel),
        new FrameworkPropertyMetadata(VirtualizationMode.Standard));

Вы действительно можете проверить это поведение позже вфайл :

//
// Set up info on first measure
//
if (HasMeasured)
{
    VirtualizationMode oldVirtualizationMode = InRecyclingMode ? VirtualizationMode.Recycling : VirtualizationMode.Standard;
    if (oldVirtualizationMode != virtualizationMode)
    {
        throw new InvalidOperationException(SR.Get(SRID.CantSwitchVirtualizationModePostMeasure));
    }
}
else
{
    HasMeasured = true;
}

, и нет способа (в соответствии с исходным кодом) установить для этого свойства HasMeasured значение False, если только вы не уничтожите и не создадите заново ListView.

0 голосов
/ 07 ноября 2019

Это так, как говорится в сообщении:

Вам не разрешено _ изменять вложенное свойство VirtualizationMode в ItemsControl после вызова Measure на панели ItemsHost.

Это означает, что если ListView уже показан используемый механизм виртуализации, и вам не разрешено его изменять.

Если вы установите Visibility из ListView в XAML на Collapsed и установите его толькопозже Visible, затем вы можете установить VirtualizationMode в коде позади желаемого значения, но только один раз (поэтому вы не сможете изменить его после того, как ListView станет видимым)!

private void Button_Click(object sender, RoutedEventArgs e)
{
    listView.SetValue(VirtualizingStackPanel.VirtualizationModeProperty, VirtualizationMode.Recycling);
    listView.Visibility = Visibility.Visible;
}

XAML:

<ListView x:Name="listView" ... Visibility="Collapsed">
...