unoplatform / uno.toolkit.ui

A set of custom controls for the WinUI and the Uno Platform not offered out of the box by WinUI, such as Card, TabBar, NavigationBar, etc.
https://platform.uno/
MIT License
82 stars 27 forks source link

Command not executed for `ListView.ItemClicked` when `ItemsSource` bound to `FeedView` #1229

Open ProphetLamb opened 2 weeks ago

ProphetLamb commented 2 weeks ago

Current behavior

The bound command is not executed.

Expected behavior

The bound command is executed.

How to reproduce it (as minimally and precisely as possible)

This minimum working sample uses the new template. Create a UNO project wit C# markup.

Add a list, a method to filter the list and a command to execute on click to the model.

public IListState<string> Fruit => ListState<string>.Value(this, () => ["Apple", "Bannana", "Citrus"]);

public IListFeed<string> FilteredFruit => Feed.Combine(Name, Fruit.AsFeed()).Select(t => t.Item2.Where(f => f.Contains(t.Item1)).ToImmutableList()).AsListFeed();

public async Task FruitClicked(object selecteditem)
{
    await Name.UpdateAsync(n => selecteditem.ToString());
}

First, confirm that the items click action is executed on the Fruit list.

new ListView()
    .Grid(row: 2)
    .ItemsSource(() => vm.Fruit)
    .IsItemClickEnabled(true)
    .CommandExtensions(b => b.Command(() => vm.FruitClicked))

Click Apple and the TextBox text is set to Apple. Great it works!

Now we wrap the ListView in a FeedView and bind the Source to FilteredFruit.

new FeedView()
    .Grid(row: 2)
    .Source(() => vm.FilteredFruit)
    .ValueTemplate(() =>
        new ListView()
            .ItemsSource(x => x.Binding("Data"))
            .IsItemClickEnabled(true)
            .CommandExtensions(b => b.Command(() => vm.FruitClicked))
    )

Side note: Please do let me know how to bind the data source using a typed binding instead of the XPath Data.

Launch the app and test the filter.

image

Great! It works.

Now click Apple. I expect the TextBox to show Apple, just like before. Nothing happens. No error. Nothing.

How do I make this work? Any advice is much appreciated. Thanks in advance!

Nuget Package:

Package Version(s):

Affected platform(s):

IDE:

Relevant plugins:

Anything else we need to know?

francoistanguay commented 2 weeks ago

I'll first acknowledge that we need both better docs and probably a better strongly typed C# Markup API for FeedView support.

When your ListView is wrapped info a FeedView, its datacontext becomes your IListFeed.

The "Data" property of that ListFeed gives you its Data state, but there's also a "Parent" property that gives you access to the parent ViewModel.

https://platform.uno/docs/articles/external/uno.extensions/doc/Learn/Mvux/FeedView.html#feedviewstates-properties

My guess is that if you replaced your Command binding for a string-based "Parent.FruitClicked", it would most probably work.

You can also use the AncestorBinding Markup Extension (we have plenty of samples in the Uno.Samples repo) to declare a binding on a parent control: https://github.com/unoplatform/Uno.Samples/blob/cae04df3bc67b9833e7b4e99623766eae9858638/reference/Commerce/src/Commerce/Views/ProductControl.xaml#L55

Docs: https://platform.uno/docs/articles/external/uno.toolkit.ui/doc/helpers/ancestor-itemscontrol-binding.html

In all cases, it's definitely a scenario we should aim to simplify.

francoistanguay commented 2 weeks ago

One more thing important noting is that if you're trying Navigation from your ItemClicked, and you're using Navigation Extensions, you can simply set the Navigation.Request on the ListView itself and it will take care of everything:

https://github.com/unoplatform/Uno.Samples/blob/cae04df3bc67b9833e7b4e99623766eae9858638/reference/Commerce/src/Commerce/Views/DealsPage.xaml#L183

eriklimakc commented 2 weeks ago

You could also set the binding source to be the FeedView and bind to the FruitClicked command in its DataContext (BindableMainModel):

new FeedView()
    .Grid(row: 2)
+   .Name(out var fv)
    .Source(() => vm.FilteredFruit)
    .ValueTemplate(() =>
        new ListView()
            .ItemsSource(x => x.Binding("Data"))
            .IsItemClickEnabled(true)
-           .CommandExtensions(b => b.Command(() => vm.FruitClicked))
+           .CommandExtensions(b => b.Command(x => x.Source(fv)
+                                                   .DataContext<BindableMainModel>()
+                                                   .Binding(bm => bm.FruitClicked)))
    )

If you want to know more about using different sources for binding expressions in C# Markup, please refer to our Source and Relative Source documentation.