MVVM 添加排序说明

MVVM Add Sort Description

我想在我已经实现的两个 Filters 之上添加 Sort 功能。

这就是我的代码目前的样子。

XAML

<Grid Grid.Row="1">
        <StackPanel Orientation="Horizontal">
            <Border  BorderThickness="1" BorderBrush="Red">
                <ComboBox
                Name="SortComboBox"
                SelectionChanged="View_SelectionChanged"
                ItemsSource="{Binding sortOptions, Mode=OneWay}" 
                SelectedValue="{Binding selectedSortOption}"
            />
            </Border>
            <ComboBox
                Name="GenreComboBox"
                SelectionChanged="View_SelectionChanged"
                ItemsSource="{Binding genreOptions, Mode=OneWay}" 
                SelectedValue="{Binding selectedGenreOption}"
            />
            <TextBox Name="SearchTextBox" Width="154" TextChanged="Search_SelectionChanged" Text="{Binding Path=searchTerm, UpdateSourceTrigger=PropertyChanged}" />
        </StackPanel>
    </Grid>    

第一个 SortComboBox 用于 sort 电影,而 GenreComboBox 根据流派过滤电影。 SearchTextBox 是另一个按关键字查找电影的过滤器。

Behind Code

    private void View_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        _moviePanelVM.DisplayMovies.View.Refresh();
    }

    private void Search_SelectionChanged(object sender, TextChangedEventArgs e)
    {
        _moviePanelVM.DisplayMovies.View.Refresh();
    }

ViewModel

 public MoviePanelViewModel()
    {
        ...
        ...

        this.DisplayMovies = new CollectionViewSource();
        this.DisplayMovies.Source = this.Movies;
        this.DisplayMovies.Filter += GenreFilter;
        this.DisplayMovies.Filter += SearchFilter;
    }

    private void GenreFilter(object sender, FilterEventArgs e)
    {
        MediaDetail media = e.Item as MediaDetail;
        if (selectedGenreOption != "All" && !media.genre.Contains(selectedGenreOption))
            e.Accepted = false;    
    }

    private void SearchFilter(object sender, FilterEventArgs e)
    {
        MediaDetail media = e.Item as MediaDetail;
        if (!media.title.ToLower().Contains(searchTerm.ToLower()))
            e.Accepted = false;
    }

SortComboBox 可能有一个选定的值 A-Z,在这种情况下我会 sort 一个特定的方式。或者它可能具有 Z-A 的值,在这种情况下我会 sort 另一种方式。

我的问题是,我应该在哪里添加 SortDescriptions 以及控制应该发生哪种排序的逻辑,以确保维护 MVVM 模式?

UPDATE

Code Behind

private void Sort_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        _moviePanelVM.SortMovies();
    }

将我的 XAML 更改为:

<ComboBox
   Name="SortComboBox"
   SelectionChanged="Sort_SelectionChanged"
   ItemsSource="{Binding sortOptions, Mode=OneWay}" 
   SelectedValue="{Binding selectedSortOption}"
/>

ViewModel

public void SortMovies()
    {
        DisplayMovies.SortDescriptions.Clear();

        switch (SelectedSortOption)
        {
            case "A-Z":
                DisplayMovies.SortDescriptions.Add(new SortDescription("title", ListSortDirection.Ascending)); break;
            case "Z-A":
                DisplayMovies.SortDescriptions.Add(new SortDescription("title", ListSortDirection.Descending)); break;
            case "Release Date":
                DisplayMovies.SortDescriptions.Add(new SortDescription("year", ListSortDirection.Descending)); break;
            case "Rating":
                DisplayMovies.SortDescriptions.Add(new SortDescription("rating", ListSortDirection.Descending)); break;
        }
    }

这行得通,但我想知道我是否应该这样做?由于 filters 只是 Refreshing view 但是 sort 我在 ViewModel.

中调用一个函数

我觉得你对MVVM设计模式的一些原则不清楚。理想情况下,您根本不希望视图隐藏任何代码,这包括事件。

您需要用命令替换事件。 Click here for a tutorial.

在下面的示例中,我没有在视图上使用任何事件,而是使用一个命令来绑定排序的更改以过滤电影。

以下是我使用的模型:

public class PropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class Movie : PropertyChangedBase
{
    private string _Name;

    public string Name
    {
        get { return _Name; }
        set 
        { 
            _Name = value;
            OnPropertyChanged("Name");
        }
    }
}

命令如下:

public class SortChangedCommand : ICommand
{
    MoviesViewModel _viewModel;

    public SortChangedCommand(MoviesViewModel viewModel)
    {
        _viewModel = viewModel;
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        _viewModel.Sort(parameter as string);
    }
}

请注意,该命令包含对视图模型的引用,并且在执行命令时它仅调用 Sort 方法。

这是视图模型:

public class MoviesViewModel : PropertyChangedBase
{
    public ObservableCollection<Movie> Movies { get; set; }

    private ObservableCollection<Movie> _FilteredMovies;

    public ObservableCollection<Movie> FilteredMovies
    {
        get { return _FilteredMovies; }
        set 
        {
            _FilteredMovies = value;

            //Have to implement property changed because in the sort method
            //I am instantiating a new observable collection.
            OnPropertyChanged("FilteredMovies");
        }
    }

    public SortChangedCommand SortChangedCommand { get; set; }

    public MoviesViewModel()
    {
        this.Movies = new ObservableCollection<Movie>();

        #region Test Data

        this.Movies.Add(new Movie()
        {
            Name = "Movie 1"
        });

        this.Movies.Add(new Movie()
        {
            Name = "Movie 2"
        });


        this.Movies.Add(new Movie()
        {
            Name = "Movie 3"
        });

        #endregion

        //Copy the movies list to the filtered movies list (this list is displayed on the UI)
        this.FilteredMovies = new ObservableCollection<Movie>(this.Movies);

        this.SortChangedCommand = new SortChangedCommand(this);
    }

    public void Sort(string sortOption)
    {
        switch (sortOption)
        {
            case "A-Z": this.FilteredMovies = new ObservableCollection<Movie>(this.Movies.OrderBy(x => x.Name)); break;
            case "Z-A": this.FilteredMovies = new ObservableCollection<Movie>(this.Movies.OrderByDescending(x => x.Name)); break;
        }
    }
}

视图模型包含两个列表,一个包含所有电影,另一个包含过滤后的电影列表。过滤后的电影将显示在 UI.

视图如下:

<Window x:Class="BorderCommandExample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ViewModels="clr-namespace:BorderCommandExample"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <ViewModels:MoviesViewModel x:Key="ViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource ViewModel}">
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <Button Command="{Binding SortChangedCommand}" CommandParameter="A-Z" 
            Content="A-Z"/>
    <Button Command="{Binding SortChangedCommand}" CommandParameter="Z-A" 
            Content="Z-A"
            Grid.Row="1"/>

    <ListBox ItemsSource="{Binding FilteredMovies}"
             DisplayMemberPath="Name"
             Grid.Row="2"/>
</Grid>

注意:我没有使用 ComboBox,因为 ComboBox 控件没有 Command 属性,但您可以轻松 get around this.

当您点击任何按钮时,命令将执行并调用排序方法。然后 UI 将更新过滤后的电影。

UI 本身可能不是您想要的,但是使用命令来实现它正是您所需要的。