![](/img/trans.png)
[英]WPF How to Bind an Image to a DataGrid column as a property of the 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.