WPF DataGrid绑定到ItemsSource项属性

时间:2017-04-21 13:38:43

标签: c# wpf xaml datagrid

我有实现IList的集合,它从服务器异步加载数据。当通过索引访问集合时,它返回一个存根对象并在后台启动数据请求。请求完成后,将刷新内部状态并调用PropertyChanged事件。现在,集合返回的项目如下所示:

public class VirtualCollectionItem 
{
    // actual entity
    public object Item { get; } 

    // some metadata properties
    public bool IsLoading { get; } 
}

问题在于我无法实现如何将此类集合绑定到DataGrid。 我想以某种方式将DataGrid.ItemsSource设置为VirtualCollectionItem的集合,使DataGrid显示实际的项目(也在SelectedItem中)并留下使用元数据的可能性(即使用{ {1}}可视化数据加载)。 我已尝试在IsLoading中设置DataGridRow.Item绑定,但尚无效。

DataGrid.RowStyle

另一种选择是将<DataGrid.RowStyle> <Style TargetType="{x:Type DataGridRow}"> <Setter Property="Item" Value="{Binding Item}" /> <Style.Triggers> <DataTrigger Binding="{Binding IsLoading}" Value="True"> <DataTrigger.Setters> <Setter Property="Background" Value="Gray" /> </DataTrigger.Setters> </DataTrigger> </Style.Triggers> </Style> </DataGrid.RowStyle> 属性展平为VirtualCollectionItem本身:

VirtualCollection

并在class VirtualCollection { // ... // wrapper around VirtualCollectionItem public IList<object> Items { get; } public IList<bool> IsLoadingItems { get; } // ... } 中使用这些属性,但我还没有意识到如何使其正常工作。

3 个答案:

答案 0 :(得分:1)

好的,所以你从服务器加载实体,但你仍然需要从ViewModel访问该集合。让我们将此功能移至服务中。该服务允许您异步加载实体列表&#39; ID,或加载特定实体详细信息:

using AsyncLoadingCollection.DTO;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

public class PeopleService
{
    private List<PersonDTO> _people;

    public PeopleService()
    {
        InitializePeople();
    }

    public async Task<IList<int>> GetIds()
    {
        // simulate async loading delay
        await Task.Delay(1000);
        var result = _people.Select(p => p.Id).ToList();
        return result;
    }
    public async Task<PersonDTO> GetPersonDetail(int id)
    {
        // simulate async loading delay
        await Task.Delay(3000);
        var person = _people.Where(p => p.Id == id).First();
        return person;
    }
    private void InitializePeople()
    {
        // poor person's database
        _people = new List<PersonDTO>();
        _people.Add(new PersonDTO { Name = "Homer", Age = 39, Id = 1 });
        _people.Add(new PersonDTO { Name = "Marge", Age = 37, Id = 2 });
        _people.Add(new PersonDTO { Name = "Bart", Age = 12, Id = 3 });
        _people.Add(new PersonDTO { Name = "Lisa", Age = 10, Id = 4 });
    }
}

GetPersonDetail方法返回DTO

public class PersonDTO
{
    public string Name { get; set; }
    public int Age { get; set; }

    public int Id { get; set; }
}

DTO可以转换为ViewModel所需的对象(我使用Prism作为MVVM框架):

using Prism.Mvvm;

public class Person : BindableBase
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set { SetProperty(ref _name, value); }
    }

    private int _age;
    public int Age
    {
        get { return _age; }
        set { SetProperty(ref _age, value); }
    }

    private int _id;
    public int Id
    {
        get { return _id; }
        set { SetProperty(ref _id, value); }
    }

    private bool _isLoaded;
    public bool IsLoaded
    {
        get { return _isLoaded; }
        set { SetProperty(ref _isLoaded, value); }
    }
}

您可以将DTO对象转换为模型,如下所示:

using DTO;
using Model;

// we might use AutoMapper instead
public static class PersonConverter
{
    public static Person ToModel(this PersonDTO dto)
    {
        Person result = new Person
        {
            Id = dto.Id,
            Name = dto.Name,
            Age = dto.Age
        };
        return result;
    }
}

这就是我们在ViewModel中定义命令(使用项检索服务)的方式:

using Helpers;
using Model;
using Prism.Commands;
using Prism.Mvvm;
using Services;
using System.Collections.ObjectModel;
using System.Linq;

public class MainWindowViewModel : BindableBase
{
    #region Fields
    private  PeopleService _peopleService;
    #endregion // Fields

    #region Constructors
    public MainWindowViewModel()
    {
        // we might use dependency injection instead
        _peopleService = new PeopleService();

        People = new ObservableCollection<Person>();
        LoadListCommand = new DelegateCommand(LoadList);
        LoadPersonDetailsCommand = new DelegateCommand(LoadPersonDetails, CanLoadPersonDetails)
            .ObservesProperty(() => CurrentPerson)
            .ObservesProperty(() => IsBusy);
    }
    #endregion // Constructors

    #region Properties

    private string _title = "Prism Unity Application";
    public string Title
    {
        get { return _title; }
        set { SetProperty(ref _title, value); }
    }

    private Person _currentPerson;
    public Person CurrentPerson
    {
        get { return _currentPerson; }
        set { SetProperty(ref _currentPerson, value); }
    }

    private bool _isBusy;
    public bool IsBusy
    {
        get { return _isBusy; }
        set { SetProperty(ref _isBusy, value); }
    }
    public ObservableCollection<Person> People { get; private set; }

    #endregion // Properties

    #region Commands

    public DelegateCommand LoadListCommand { get; private set; }
    private async void LoadList()
    {
        // reset the collection
        People.Clear();

        var ids = await _peopleService.GetIds();
        var peopleListStub = ids.Select(i => new Person { Id = i, IsLoaded = false, Name = "No details" });

        People.AddRange(peopleListStub);
    }

    public DelegateCommand LoadPersonDetailsCommand { get; private set; }
    private bool CanLoadPersonDetails()
    {
        return ((CurrentPerson != null) && !IsBusy);
    }
    private async void LoadPersonDetails()
    {
        IsBusy = true;

        var personDTO = await _peopleService.GetPersonDetail(CurrentPerson.Id);
        var updatedPerson = personDTO.ToModel();
        updatedPerson.IsLoaded = true;

        var oldPersonIndex = People.IndexOf(CurrentPerson);
        People.RemoveAt(oldPersonIndex);
        People.Insert(oldPersonIndex, updatedPerson);
        CurrentPerson = updatedPerson;

        IsBusy = false;
    }

    #endregion // Commands
}

最后,View可能就像这样简单:

<Window x:Class="AsyncLoadingCollection.Views.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:prism="http://prismlibrary.com/"
    Title="{Binding Title}"
    Width="525"
    Height="350"
    prism:ViewModelLocator.AutoWireViewModel="True">
<StackPanel>
    <!--<ContentControl prism:RegionManager.RegionName="ContentRegion" />-->
    <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
        <Button Width="100"
                Margin="10"
                Command="{Binding LoadListCommand}"
                Content="Load List" />
        <Button Width="100"
                Margin="10"
                Command="{Binding LoadPersonDetailsCommand}"
                Content="Load Details" />
    </StackPanel>
    <TextBlock Text="{Binding CurrentPerson.Name}" />
    <DataGrid CanUserAddRows="False"
              CanUserDeleteRows="False"
              ItemsSource="{Binding People}"
              SelectedItem="{Binding CurrentPerson,
                                     Mode=TwoWay}">
        <DataGrid.RowStyle>
            <Style TargetType="{x:Type DataGridRow}">
                <!--<Setter Property="Item" Value="{Binding Item}" />-->
                <Style.Triggers>
                    <DataTrigger Binding="{Binding IsLoaded}" Value="False">
                        <DataTrigger.Setters>
                            <Setter Property="Background" Value="DarkGray" />
                        </DataTrigger.Setters>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </DataGrid.RowStyle>
    </DataGrid>
</StackPanel>
</Window>

答案 1 :(得分:0)

您可以将VirtualCollection直接绑定到DataGrid.ItemsSource属性。然后绑定SelectedItem属性:

<DataGrid ItemsSource="{Binding MyVirtualCollectionList}" SelectedItem={Binding SelectedItem, Mode=TwoWay} />

然后是ViewModel:

public class MyViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private VirtualCollection _myVirtualCollectionList;

    public VirtualCollection MyVirtualCollectionList
    {
        get { return _myVirtualCollectionList; }
        set
        {
            _myVirtualCollectionList = value;
            OnPropertyChanged();
        }
    }

    private VirtualCollectionItem _selectedItem;

    public VirtualCollectionItem SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;
            OnPropertyChanged();
        }
    }
}

您必须在加载列表后触发OnPropertyChanged事件,并且必须从MyViewModel对象(我认为您不这样做)执行此操作!您也可以使用ObservableCollection(可以扩展它)。然后,您不需要OnPropertyChange事件。从集合中添加/删除项目会自动通知UI。

SelectedItem独立于列表工作。在DataTemplate中,您将使用{Binding IsLoading}和{Binding Item.SomeProperty}。

答案 2 :(得分:0)

我决定使用ViewModel包装DataGrid:

public class DataGridAsyncViewModel : Notifier
{
  public VirtualCollection ItemsProvider { get; }

  private VirtualCollectionItem _selectedGridItem;

  public VirtualCollectionItem SelectedGridItem
  {
    get { return _selectedGridItem; }
    set { Set(ref _selectedGridItem, value); }
  }

  public object SelectedItem => SelectedGridItem?.IsLoading == false ? SelectedGridItem?.Item : null;

  public DataGridAsyncViewModel([NotNull] VirtualCollection itemsProvider)
  {
    if (itemsProvider == null) throw new ArgumentNullException(nameof(itemsProvider));
    ItemsProvider = itemsProvider;
  }
}

并将其绑定到DataGrid:

<DataGrid DataContext="{Binding DataGridViewModel}" 
          SelectedItem="{Binding SelectedGridItem}" 
          ItemsSource="{Binding ItemsProvider}" >
  <DataGrid.RowStyle>
    <Style TargetType="{x:Type DataGridRow}">
      <Style.Triggers>
        <DataTrigger Binding="{Binding IsLoading}" Value="True">
          <Setter Property="Background" Value="LightGray" />
        </DataTrigger>
      </Style.Triggers>
    </Style>
  </DataGrid.RowStyle>
  <DataGrid.Columns>
    <DataGridTextColumn Header="..." Binding="{Binding Item.SomeValue}" />
  </DataGrid.Columns>
</DataGrid>