2017-04-21 347 views
0

我有收集实现IList从服务器异步加载数据。当通过索引访问集合时,它返回一个存根对象并在后台启动数据请求。请求完成收集后刷新内部状态并调用PropertyChanged事件。现在由集合返回的项目是这样的:WPF DataGrid绑定到ItemsSource项目属性

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

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

的问题是,我不知道如何将此类收集绑定到DataGrid。 我想以某种方式将DataGrid.ItemsSource设置为VirtualCollectionItem的集合,使DataGrid显示实际项目(也在SelectedItem中),并且可以使用元数据(即使用IsLoading来可视化数据加载)。 我试着在DataGrid.RowStyle中设置DataGridRow.Item绑定,但是这些都没有奏效。

<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; } 

    // ... 
} 

DataGrid使用这些属性,但我不知道如何使这项工作。

回答

0

我决定换用视图模型在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> 
0

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

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

然后视图模型:

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}。

+0

在我的情况DataGrid的项目/的SelectedItem(S)必须从服务器中实际加载的实体,这样我就可以使用它无需重写吨现有的代码库。另外我在需要的地方调用PropertyChanged&CollectionChanged事件。 – ilivit

1

好的,所以您从服务器加载实体,但仍需要访问ViewModel中的集合。让我们把这个功能转移到一个服务上。该服务允许你异步加载实体的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(我用棱镜作为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); } 
    } 
} 

你可以在c onvert的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> 
相关问题