Home > Software engineering >  Is it possible to call a ViewModel method in WPF MVVM by clicking the ListView item?
Is it possible to call a ViewModel method in WPF MVVM by clicking the ListView item?

Time:10-12

Is it possible to do it using classic libraries? I have a ListView

<ListView Background="#222222"
       SelectedItem="{Binding SelectedAsset}"
       ScrollViewer.CanContentScroll="False"
       ItemsSource="{Binding Assets}"
       Style="{StaticResource ListStyle}">
</ListView>

And the ViewModel like this

public class HomeViewModel
{
    public AssetOverview SelectedAsset { get; set; }
    private ObservableCollection<AssetOverview> _assets;
    public ObservableCollection<AssetOverview> Assets
    {
        get { return _assets; }
        set
        {
            if (_assets == value)
            {
                return;
            }
            _assets = value;
            RaisePropertyChanged("MyAssets");
        }
    }
    private static HttpClient _client;
    public event PropertyChangedEventHandler PropertyChanged;
    public HomeViewModel()
    {
        Assets = new ObservableCollection<AssetOverview>();

        try
        {
            _client = new HttpClient();
            _client.DefaultRequestHeaders.Accept.Clear();
            _client.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));
        }
        catch
        {

        }
        PopulateCollection();
    }

    async void PopulateCollection()
    {
        var client = new RestClient();

        var request = new RestRequest("https://cryptingup.com/api/assetsoverview");
        request.AddHeader("Accept", "application/json");
        request.AddHeader("Content-Type", "application/json");

        var response = client.GetAsync(request).GetAwaiter().GetResult();
        var date = JsonConvert.DeserializeObject<CurrencyModelOverview>(response.Content);
        for (int i = 0; Assets.Count < 10; i  )
        {
            if (date.Assets[i].Name != "")
            {
                Assets.Add(date.Assets[i]);
            }
        }
    }

    private void RaisePropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

For example I want to add a method to change the View to the details page of the clicked item, can I do it? For now, my code is only saving the value of the clicked item into the SelectedAsset property. I also tried to do it with the button and Command property, but on the click I am either copying the SelectedItem into the SelectedAsset property, or pressing the button and calling the method, so in my approach I had to first click the item, and them click the details button.

CodePudding user response:

HomeViewModel should look like this

public class HomeViewModel : INotifyPropertyChanged 
{
    private AssetOverview _selectedAsset;

    public AssetOverview SelectedAsset
    {
        get => _selectedAsset;
        set
        {
            // if (_selectedAsset == value) return;
            _selectedAsset = value;
            // TODO: call some method
        }
    }

    //.. 

} 

So you can call whatever methods you want in SelectedAsset's setter.

CodePudding user response:

I woudl sugget using InputBindings:

<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800" x:Name="Root">
    <Grid DataContext="{Binding ElementName=Root, Path=ViewModel}">
        <ListView>
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.InputBindings>
                            <MouseBinding MouseAction="LeftClick" Command="{Binding  ElementName=Root, Path=ViewModel}" CommandParameter="{Binding}"></MouseBinding>
                        </Grid.InputBindings>
                        <TextBlock>Some text</TextBlock>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Window>

You can use double click,right click, middle click etc. There are also keyboard binding, useful when you want to stick to MVVM, but handle for example ctrl v.

Just make sure your data context for ViewModel is correct - in this case it is a property of your MainWindow class

EDIT: I think I just understood your question :D

Without any additional libraries you cannot "react" to things happening, you have to make more and more complicated setters. That has aq drawback of always running on the UI thread, and you cannot await in there.

I strongly recomment going reactive with ReactiveUI, so you can do:

this.WhenAnyValue(x => x.SelectedItem).Do(item => doStuff());

// or even
this.WhenAnyValue(x => x.SelectedItem).InvokeCommand(MyCommand);

You can compose quite complex bevaviours based on that, control the threading, do debouncing/throttling, delays etc, and still be able to write unit tests that don't have to wait the full delay time (they have a TestScheduler for that).

Highly recommend to take a dive! You can do the obj.WhenAnyValue() on any object that implements INotifyPropertyChanged, so you don't have to rewrite your already made ViewModels.

  • Related