高级MVVM逻辑 - 有可能吗?

时间:2016-07-30 19:19:41

标签: c# wpf mvvm data-binding

我正在尝试了解WPF视图模型。

假设我有一个项目列表。在某些情况下,我想填充列表并指定应该选择的项的值。

在其他情况下,我只想重新填充列表,控件应尝试选择之前选择的项目。

在其他情况下,我想在删除项目后重新填充列表。在这种情况下,无法选择之前选择的项目,我希望代码在相同的列表位置选择项目(如果最后一项被删除,则选择较低的项目)。

通常情况下,我会填充列表,如上所述。但如果我必须绑定ItemsSourceSelectedItem,这似乎很尴尬。

这种逻辑可以在MVVM设计中实现吗?

3 个答案:

答案 0 :(得分:2)

  

这种逻辑可以在MVVM设计中实现吗?

不确定。
但是您应该注意,idx = [ el_idx for el_idx, el in enumerate(theList) if el.IsSomething() ] [ theList[i].SetIt(False) for i in idx ] 中设置的值必须出现在SelectedItem中。所以,你的物品必须有一些身份。例如,想象从Web服务加载的客户列表:

ItemsSource

在实施public class Customer { public int Id { get; set; } public string Name { get; set; } } 排序时,您可能需要重新选择之前选择的客户:

RefreshCommand

类似的方法可用于您提到的其他案例。

答案 1 :(得分:1)

您可能需要考虑为INotifyCollectionChanged使用ListBox.ItemsSource界面。如果您正在处理专门的集合,您可能需要自己实现。但是,您可以通过使用(或子类化)ObservableCollection<T>来实现上述接口。无论哪种方式,使用此接口都允许ListBox在更新基础集合时自动更新。

答案 2 :(得分:1)

我想与您分享这个示例项目,作为如何将MVVM与集合一起使用的简单演示。我希望你觉得它很有用。

因此,您要做的第一件事就是定义一个ObservableObject类,使您可以访问INotifyPropertyChanged。这是一个非常基本的实现,但它在大多数情况下都能很好地工作。

using System.ComponentModel;
namespace WpfApplication8
{
    public class ObservableObject :  INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public void RaisePropertyChanged(string property)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
        }
    }
}

我们将在我们的视图模型中实现此功能,以及我们希望使用的任何自定义类型INotifyPropertyChanged

此演示将使用Book课程。 Book类只会包含几个字段,我们还会在同一个文件中包含ICommand实现,以便以后更轻松地进行绑定。每当ICommand专门应用于Type时,我就会使用Type的代码文件来整齐地组合在一起。

using System;
using System.Windows.Input;

namespace WpfApplication8
{
    public class Book : ObservableObject
    {

        private string _title = "";
        public string Title
        {
            get
            {
                return _title;
            }
            set
            {
                _title = value;
                RaisePropertyChanged("Title");
            }
        }

        private string _author = "";
        public string Author
        {
            get
            {
                return _author;
            }
            set
            {
                _author = value;
                RaisePropertyChanged("Author");
            }
        }

    }
    public abstract class BookCommand : ICommand
    {
        public abstract void Execute(object parameter);
        public virtual bool CanExecute(object parameter)
        {
            return parameter is Book;
        }
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
    }
}

接下来,我们需要一个ObservableCollection实现,让我们完全控制添加或删除对象时会发生什么。我们还希望ICommand个对象添加删除图书。

using System.Collections.Specialized;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System;

namespace WpfApplication8
{
    public class BookCollection : ObservableCollection<Book>
    {
        private ObservableObject parent;
        private string name = "";
        public BookCollection(ObservableObject parent, string name)
        {
            this.parent = parent;
            AddBook = new CMD_AddBookToCollection(this);
            RemoveBook = new CMD_RemoveBookFromCollection(this);
            CollectionChanged += (s, e) =>
            {
                if(e.Action == NotifyCollectionChangedAction.Add)
                {
                    // what will i do when a new item is added to the collection?

                }
                else if(e.Action == NotifyCollectionChangedAction.Remove)
                {
                    // what will i do when an item is removed from the collection?
                    if(Items.Count > 0)
                    {
                        if(Items.Count < _lastSelectedIndex)
                        {
                            SelectedItem = Items[_lastSelectedIndex];
                        }
                        else
                        {
                            SelectedItem = Items[Items.Count - 1];
                        }
                    }
                }
            };
        }
        private int _lastSelectedIndex = -1;
        private Book _selectedItem = null;
        public Book SelectedItem
        {
            get
            {
                return _selectedItem;
            }
            set
            {
                _selectedItem = value;
                _lastSelectedIndex = Items.IndexOf(_selectedItem);
                parent.RaisePropertyChanged(name);
            }
        }

        public ICommand AddBook { get; set; }
        public ICommand RemoveBook { get; set; }

    }
    public abstract class BookCollectionCommand : BookCommand
    {
        public BookCollection Collection { get; set; }
        public BookCollectionCommand(BookCollection collection)
        {
            this.Collection = collection;
        }
    }
    public class CMD_RemoveBookFromCollection : BookCollectionCommand
    {
        public CMD_RemoveBookFromCollection(BookCollection collection) : base(collection) { }
        public override void Execute(object parameter)
        {
            base.Collection.Remove((Book)parameter);
        }
    }
    public class CMD_AddBookToCollection : BookCollectionCommand
    {
        public CMD_AddBookToCollection(BookCollection collection) : base(collection) { }
        public override void Execute(object parameter)
        {
            Book template = parameter as Book;
            Book book = new Book()
            {
                Author = template.Author,
                Title = template.Title
            };
            base.Collection.Add(book);
            template.Author = "";
            template.Title = "";
        }
    }
}

一开始看起来似乎势不可挡,但是当你逐步分解它时,它并不是那么糟糕。 CollectionChanged事件使我们能够在从集合中添加或删除项目时添加自定义功能。例如,这可以很容易地在删除某些内容后从列表中选择一个新项目。我们还创建了一个BookCollectionCommand抽象类作为我们具体ICommand实现的基础。这样做的原因纯粹是为了减少冗余,根据项目的不同,这可能并不理想,但这里效果很好。

接下来我们需要的是一个根视图模型,它将直接绑定到我们的视图。

namespace WpfApplication8
{
    public class ViewModel : ObservableObject
    {
        public ViewModel()
        {
             MyBooks = new BookCollection(this, "MyBooks");
        }
        public BookCollection MyBooks { get; set; }
        public Book NewBookTemplate { get; set; } = new Book();
    }
}

请注意NewBookTemplate字段。您可能想知道为什么我没有将CMD_AddBookToCollection的参数直接推送到集合中。此模式中的参数只是一个模板,一个抛弃对象,用于将视图中的输入传递到视图模型中。这给了我们两个非常重要的自由:我们可以在将对象传递到我们的集合之前清理对象,并且我们可以避免为#34添加一本特殊的视图模型添加一本新书&#34;形成。 Book类本身可以充当视图模型,既可以节省我们的工作,又可以让我们自由地在任何其他视图中重用该对象。现在我们只需要实现一个基本视图并对其进行全面测试。

<Window x:Class="WpfApplication8.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:WpfApplication8"
        x:Name="main_window"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="60" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" Grid.Row="0">
            <StackPanel.Resources>
                <Style TargetType="FrameworkElement" x:Key="default_styles">
                    <Setter Property="Width" Value="100" />
                    <Setter Property="Height" Value="30" />
                    <Setter Property="HorizontalAlignment" Value="Center" />
                    <Setter Property="VerticalAlignment" Value="Center" />
                    <Setter Property="Margin" Value="5" />
                </Style>
                <Style TargetType="TextBlock" BasedOn="{StaticResource default_styles}" >
                    <Setter Property="Width" Value="50" />
                </Style>
                <Style TargetType="TextBox" BasedOn="{StaticResource default_styles}" />
                <Style TargetType="Button" BasedOn="{StaticResource default_styles}" />
            </StackPanel.Resources>
            <TextBlock Text="Title: " />
            <TextBox Text="{Binding NewBookTemplate.Title, UpdateSourceTrigger=PropertyChanged}" />
            <TextBlock Text="Author: " />
            <TextBox Text="{Binding NewBookTemplate.Author, UpdateSourceTrigger=PropertyChanged}" />
            <Button Content="Add Book " Command="{Binding MyBooks.AddBook}" CommandParameter="{Binding NewBookTemplate}" />
            <Button Content="Remove Book " Command="{Binding MyBooks.RemoveBook}" CommandParameter="{Binding MyBooks.SelectedItem}" />
            <TextBlock Text="Count: " />
            <TextBlock Text="{Binding MyBooks.Count}" />
        </StackPanel>
        <ListBox Grid.Row="1" ItemsSource="{Binding MyBooks}" SelectedItem="{Binding MyBooks.SelectedItem, UpdateSourceTrigger=PropertyChanged}" >
            <ListBox.InputBindings>
                <KeyBinding Key="Delete" Command="{Binding MyBooks.RemoveBook}" CommandParameter="{Binding MyBooks.SelectedItem}" />
            </ListBox.InputBindings>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Title}" />
                        <TextBlock Text=", " />
                        <TextBlock Text="{Binding Author}" />
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

嗯,一切都在我的身上。我希望这个例子有助于使MVVM更容易消化。快乐的编码!