listviewuwpbindingstoragefilexbind

FileInformation.RenameAsync() breaks binding compared to StorageFile.RenameAsync()


What makes the following code break the binding of a FileInformation DataTemplate of a ListView when the clicked item is renamed? And then, how to get the proper notification to the view that the FileInformation has been renamed?

Consider a folder with 2 images, 1.jpg and 2.jpg. This folder is indexed by Windows 10 1909 and the UWP app is version 1903.

After the user selected a folder, we create a FileInformationFactory and retrieve the VirtualizedFiles object and set it to the source of CollectionViewSource. The view of the latter populates an ICollectionView which is OneWay bound to the ItemsSource of a ListView.

We display in the listview each file Name and file FolderRelativeId of the FileInformation. A rename button takes the ListView.Selecteditem and renames it as 1.jpg, with the option of GenerateUniqueName.

There are two methods at hand.

1) The most natural one given the code is to use FileInformation.RenameAsync(), since the selected item already is a FileInformation.

2) Get the Storagefile from folder.GetFileAsync, where the name parameter is given by the FileInformation.Name, and then call the StorageFile.RenameAsync.

In both methods, as we rename the file, the listview is updated, as expected. However, this does not last long as I keep renaming, even if I give the time for the file to be renamed. Indeed, in the first scenario I can rename the file, but, as I keep renaming , at some unclear point, the listview seems stuck to the previous renamed name and mismatches the FolderRelativeId. For instance, the name of the file appears as "1 (2).jpg" while FoldeRelativeId ends with "1 (3).jpg.jpg". The listview does not recognize the item as part of the ListView as I cannot select it anymore and renaming throws a caught exception.

This problem does not seem to appear in scenario 2, why? How can I use scenario 1 (i.e. stick to FileInformation for renaming) and keep notifications alive to update the UI without this bug?

An additional question would be how to keep the selected item the same after the file has been renamed, because often (not always!) the selected item is lost (index=-1), probably because the ICollectionview has been reset owing to notifications from the file system.

To see the bug, uncomment the line in the RenameButtonClick event (and comment the rename call from the storagefile a few lines above). await fileInformation.RenameAsync("1.jpg", NameCollisionOption.GenerateUniqueName);

Any help is appreciated as I am struggling on this issue since a few days now. Thanks

public sealed partial class Scenario5 : Page, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
      => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    public Scenario5()
    {
        this.InitializeComponent();
    }

    private ICollectionView _fileCollectionView;
    private StorageFolder _folder;
    private QueryOptions _queryOptions;
    private StorageFileQueryResult _query;
    private FileInformationFactory _fileInformationFactory;

    public CollectionViewSource CollectionViewSource { get; set; } = new CollectionViewSource();
    public ICollectionView ItemCollectionView
    {
        get { return _fileCollectionView; }
        set
        {
            if (_fileCollectionView != value)
            {
                _fileCollectionView = value;
                OnPropertyChanged(nameof(ItemCollectionView));
            }
        }
    }
    public ObservableCollection<string> Information { get; private set; } = new ObservableCollection<string>();
    private async void FolderPickerButton_Click(object sender, RoutedEventArgs e)
    {
        var _pickedFolder = await PickFolderAsync();
        if (_pickedFolder == null)
        {
            return;
        }
        Information.Clear();
        _folder = _pickedFolder;
        _queryOptions = new QueryOptions
        {
            FolderDepth = FolderDepth.Deep,
            IndexerOption = IndexerOption.UseIndexerWhenAvailable,
        };

        _query = _folder.CreateFileQueryWithOptions(_queryOptions);

        _fileInformationFactory = new FileInformationFactory(_query, ThumbnailMode.SingleItem, 160,
            ThumbnailOptions.UseCurrentScale, delayLoad: false);

        var _vector = _fileInformationFactory.GetVirtualizedFilesVector();

        CollectionViewSource.Source = _vector;
        ItemCollectionView = CollectionViewSource.View;


    }

    private static async Task<StorageFolder> PickFolderAsync()
    {
        var folderPicker = new FolderPicker
        {
            SuggestedStartLocation = PickerLocationId.Desktop,
            ViewMode = PickerViewMode.Thumbnail
        };

        folderPicker.FileTypeFilter.Add("*");

        var _pickedFolder = await folderPicker.PickSingleFolderAsync();
        return _pickedFolder;
    }

    private void ListView_ItemClick(object sender, ItemClickEventArgs e)
    {
        if (e.ClickedItem is FileInformation fileInformation)
        {
            Information.Add($"Click {fileInformation.Name}\n{fileInformation.FolderRelativeId}");
        }
    }

    private async void RenameButton_Click(object sender, RoutedEventArgs e)
    {
        if (itemCollectionGridView.SelectedItem is FileInformation fileInformation)
        {
            Information.Add($"Selected item: {fileInformation.Name}\n{fileInformation.FolderRelativeId}");

            try
            {
                var storageFile = await _folder.GetFileAsync(fileInformation.Name);
                await storageFile.RenameAsync("1.jpg", NameCollisionOption.GenerateUniqueName);
                Information.Add($"Renamed storagefile {storageFile.Name}\n{storageFile.FolderRelativeId}");

                //await fileInformation.RenameAsync("1.jpg", NameCollisionOption.GenerateUniqueName);
                Information.Add($"Renamed FileInformation result {fileInformation.Name}\n{fileInformation.FolderRelativeId}");
            }
            catch (Exception ex)
            {
                Information.Add($"{ex.Message}\n" +
                    $"{fileInformation.Name}\n{fileInformation.FolderRelativeId}");
            }
        }
    }

    private void ClearButton_Click(object sender, RoutedEventArgs e)
    {
        Information.Clear();
    }
}

And the XAML

<Page
x:Class="Virtualization.Scenario5"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Virtualization"
xmlns:ba="using:Windows.Storage.BulkAccess"    
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="auto"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="auto"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="auto"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="48"/>
    </Grid.ColumnDefinitions>
    <StackPanel Grid.ColumnSpan="1" Orientation="Horizontal"
                BorderBrush="Blue" BorderThickness="0,0,2,2">
        <AppBarButton Icon="Folder"
                      LabelPosition="Collapsed"
                      Click="FolderPickerButton_Click"/>
        <AppBarButton Icon="Rename"
                      LabelPosition="Collapsed"
                      Click="RenameButton_Click"/>
        <AppBarButton Icon="Clear"
                      LabelPosition="Collapsed" Label="Select Folder"
                      Click="ClearButton_Click"/>
    </StackPanel>
    <ListView x:Name="itemCollectionGridView" Grid.Row="1" Grid.Column="1"
              ItemsSource="{x:Bind ItemCollectionView, Mode=OneWay}" IsItemClickEnabled="True"
              ItemClick="ListView_ItemClick">
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="ba:FileInformation">
                <StackPanel MinHeight="100">
                    <TextBlock Text="{Binding Name}" TextWrapping="WrapWholeWords"/>
                    <TextBlock Text="{Binding Path}" TextWrapping="WrapWholeWords"/>
                    <TextBlock Text="{Binding FolderRelativeId}" TextWrapping="WrapWholeWords"/>

                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

    <ListView Grid.Row="1" Grid.Column="2" ItemsSource="{x:Bind Information, Mode=OneWay}">
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <ItemsStackPanel ItemsUpdatingScrollMode="KeepLastItemInView" />
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}"/>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

</Grid>


Solution

  • FileInformation.RenameAsync() breaks binding compared to StorageFile.RenameAsync()

    The problem looks like a bug and I could reproduce this issue, In general we often storageFile.RenameAsync method to rename the file. Please try to use storageFile.RenameAsync to replace, and you could report this with windows feed back hub app.

    An additional question would be how to keep the selected item the same after the file has been renamed, because often (not always!) the selected item is lost (index=-1), probably because the ICollectionview has been reset owing to notifications from the file system.

    It will take some time to build the new index for the file then notify the ICollectionview, the better way is add the folder to the FutureAccessList, and re-create the ICollectionview.