dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.24k stars 1.76k forks source link

CollectionView and ListView are setting SelectedItem to null on control load. #8572

Open mobycorp opened 2 years ago

mobycorp commented 2 years ago

Description

This has been driving me nuts all day! I have a page with a list where I want the first item to be highlighted on page load. I have watched my code in debug and am convinced that the control is setting SelectedItem to null AFTER I have programmatically set it on page load as there is a spurious second call to the OnPropertyChanged event handler after I initially set the property in code. When I looked at the call stack there is no code that I developed making this second call...

There is an article in the MAUI documentation that describes exactly what I want to do:

https://docs.microsoft.com/en-us/dotnet/maui/user-interface/controls/collectionview/selection#single-pre-selection

I created a stub application using the code verbatim. I did a test where I was testing the CollectionView and ListView controls side by side and was able to confirm the error is in both controls. I have attached a demo repo that demonstrates the issue(s) here:

https://github.com/mobycorp/ListViewErrors

Notice the call to OnPropertyChanged incremented the counters twice. I only called the properties to be changed once. Here is a screenshot of the application after startup and I have not clicked any item in the two lists:

Screenshot 2022-07-06 175357

In this repo, you will see that I am only setting the selected item once in code:

public MonkeysViewModel () {

    Monkeys.Add (new Monkey { Name = "Steve" });
    Monkeys.Add (new Monkey { Name = "Bill" });
    Monkeys.Add (new Monkey { Name = "Tom" });
    Monkeys.Add (new Monkey { Name = "Dick" });
    Monkeys.Add (new Monkey { Name = "Harry" });

    SelectedMonkeyCv = Monkeys.Skip (3).FirstOrDefault ();
    SelectedMonkeyLv = Monkeys.Skip (2).FirstOrDefault ();
}

I did not use any helper NuGet packages such as CommunityToolkit.MVVM as I didn't want code external to what I developed to affect anything. To demonstrate that the control itself is setting the SelectedItem to null, I have put a counter in that I increment whenever the SelectedMonkeyCv or SelectedMonkeyLv properties are changed that I display over the list controls:

Screenshot 2022-07-06 175602

According to the documentation for CollectionView I indicated in the above link, items 4 and 3, respectively, SHOULD BE preselected, but they are not because of the subsequent nulling of the SelectedItem view which kills the preselection.

After I have clicked a couple of times in each list control, the counters got incremented and the controls did not set the SelectedItem property to null:

Screenshot 2022-07-06 175636

And, while I have your attention, there is another issue that I consider a bug. If you look at the ListView items, you can see the right-hand side of the display template has rounded corners. I do not use any controls where the corner radius can be set. In addition to that, I went into Styles.xaml and commented out ALL occurrences of any radius settings. This corner radius setting has to be being made in the control code itself. Please do not apply any styles to any control outside of those set in Styles.xaml!

Steps to Reproduce

I have attached a repo that demonstrates this issue.

Version with bug

6.0.400 (current)

Last version that worked well

Unknown/Other

Affected platforms

Windows

Affected platform versions

net6.0-windows10.0.19041.0

Did you find any workaround?

No!!! :-(

Relevant log output

No response

jsuarezruiz commented 2 years ago

Verified.

Pnixys commented 2 years ago

I'm facing the same problem here. I found a little workaround with a initialize method in the view model who loads the selected item again (this method is called before all initialization, I can do this because I encapsulate the navigation system into a service). But the visual state of the element stays at normal. If I click on it the visual state didn't change, but if I click on a another element and I click again on the first one the visual state is updated.

ss1969 commented 2 years ago

@mobycorp have you found a workaround or fix for this bug? I've strucked in the same situation.

Pnixys commented 2 years ago

@ss1969 I have one if you want. The item displayed have a boolean property IsSelected. When I display the page, in the xaml part I bind this property to a converter that change the style according to the boolean. Every time you click on a element, you change the IsSelected property of your old element (or not if you want a multiple selected element) and change the IsSelected element of the new selected element.

I hope this help, if you need more details or if I am not clear don't hesitate, I will be happy to keep helping ^^ (I apologize for my terrible English, I'm not a native speaker)

ss1969 commented 2 years ago

@Pnixys Coincidentally, I've add this "IsSelected" property in my Model also, to make the View to be affected. And in the setter of "SelectedItems" of collectionview, I added a null check, if value == null then directly return. Have you modified the setter too? btw, I'm not a native speaker either :)

mattleibow commented 2 years ago

Just adding a repro code as well with things:

C#

string selectedMonkey;

public MainPage()
{
    InitializeComponent();

    selectedMonkey = Monkeys.Skip(3).FirstOrDefault();

    BindingContext = this;
}

public ObservableCollection<string> Monkeys { get; private set; } =
    new ObservableCollection<string>(new string[] { "First", "Second", "Third", "Fourth", "Fifth" });

public string SelectedMonkey
{
    get => selectedMonkey;
    set
    {
        if (selectedMonkey != value)
            selectedMonkey = value;
    }
}

XAML

<CollectionView ItemsSource="{Binding Monkeys}"
            SelectionMode="Single"
            SelectedItem="{Binding SelectedMonkey}">
    <CollectionView.ItemTemplate>
        <DataTemplate>
            <Label Text="{Binding .}" />
        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>
Pnixys commented 2 years ago

@Pnixys Coincidentally, I've add this "IsSelected" property in my Model also, to make the View to be affected. And in the setter of "SelectedItems" of collectionview, I added a null check, if value == null then directly return. Have you modified the setter too? btw, I'm not a native speaker either :)

Yup, every time I change the selected item I need to set the slected property, I do that in the setter.

rsorochak commented 1 year ago

Hi, guys According to CollectionView document: The SelectionChanged event can be fired by changes that occur as a result of changing the SelectionMode property.

So i found a workaround you can use till it is fixed completely

_collectionView.Loaded += (sender, args) =>
{
      _collectionView.SelectionMode = SelectionMode.Single;
};
KieranMaclagan commented 1 year ago

I'm running into this on a Picker. I have a popup form with a backing view model, bound SelectedItems, and SelectedItem. When I go to edit the form, SelectedItems is still populated but SelectedItem gets set to null. I've had to re-set any Picker items after loading the form from the calling page, which re-fires all the OnPropertyChanged events. Anyone have another workaround, or even better an actual fix?

EDIT: I stopped using the [ObservableProperty] tag, went with the manual get/set, and in the set checked if not null.

HobDev commented 1 year ago

I am facing this issue on Windows with the current Visual Studio Version 17.5.4. The workaround is to return from the method if the SelectedItem is null.

XamlTest commented 1 year ago

Verified this on Visual Studio Enterprise 17.7.0 Preview 1.0. Repro on Windows 11 with below Project: 8572.zip

On .NET 7, the original issues can be reproduced, but the rounded corners issue only reproduced on .NET 6, not reproduced on .NET 7(Not comment out any radius settings in Style.xaml). SelectedItem

KleinPan commented 11 months ago

any body process it?

Ghostbird commented 8 months ago

I think this is still the case. I've just spent two days trying to figure out why our settings are not correctly persisted. It turns out that whenever you open the page, each Picker gets its SelectedItem and ItemsSource bound, and the fact that the ItemsSource is bound, always causes the SelectedItem to be set to null, which triggers a save of the settings, and TADA! you've lost all settings that are set using Pickers.

Ghostbird commented 8 months ago

Nevermind, I found out this gem in the documentation:

A Picker can be initialized to display a specific item by setting the SelectedIndex or SelectedItem properties. However, these properties must be set after initializing the ItemsSource collection.

So what I want is not supported. What is the point making ItemsSource bindable, if anything but a one-time set, at a known point in time completely messes up your data? The supported use case is exactly the single one where we don't need to, nor should, use bindings.

Inrego commented 8 months ago

If both values are set at the same time (for example sub-properties of an initialized viewmodel), the order of the properties in xaml does matter

Ghostbird commented 8 months ago

We wrote our own picker that requires us to handle the ItemsSource / SelectedItem relationship explicitly. This works better for all our use cases so far.