1

当我尝试VirtualizationMode在我ListView的 to上设置时,我Recycling从标题中得到错误:

在 ItemsHost 面板上调用 Measure 后,无法更改 ItemsControl 上的 VirtualizationMode 附加属性。

我正在尝试以编程方式设置附加属性,但是当我尝试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);
}

编辑:我将listview传递给viewmodel的窗口代码是:

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

以及代码的视图模型部分(减去 getter 和 setter 以减少占用的空间):

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
    }
4

2 回答 2

1

代码源表明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.

于 2019-11-07T15:54:49.693 回答
0

It is so as the message says:

Your not allowed to _change the VirtualizationMode attached property on an ItemsControl after Measure is called on the ItemsHost panel.

This means, that if ListView is already shown virtualizing mechanism being used and you are not allowed to change it.

If you set Visibility of ListView in XAML to Collapsed and set it only later to the Visible, then you can set the VirtualizationMode in code behind to wished value, but only once(so you can't change it after ListView become visible)!

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">
于 2019-11-07T16:00:32.067 回答