簡體   English   中英

WPF DataGrid綁定到ItemsSource項目屬性

[英]WPF DataGrid bind to ItemsSource items property

我有實現IList的集合,該集合從服務器異步加載數據。 當索引訪問該集合時,它將返回一個存根對象並在后台啟動數據請求。 請求完成后,集合將刷新內部狀態並調用PropertyChanged事件。 現在,集合返回的項目如下所示:

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使用這些屬性,但是我還沒有意識到如何使它起作用。

好的,因此您可以從服務器加載實體,但是仍然需要從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所需的對象(並且我使用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對象轉換為Model:

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>

您可以將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與列表獨立工作。 在數據模板的一行中,將使用{Binding IsLoading}和{Binding Item.SomeProperty}。

我決定用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>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM