-->

Wpf ICollectionView Binding item cannot resolve pr

2020-07-02 12:41发布

问题:

I have bound a GridView with an ICollectionView in the XAML designer the properties are not known because the entity in the CollectionView have been transformed into type Object and the entity properties can't be accessed, it runs fine no error but the designer shows it as an error, if I bind to the collection I can access the properties fine

Example the entity is a Person with a string Name property I place them in an ObservableCollection<Person> and get the view from it and bind it to the GridView.ItemsSource now when I try to set the column header DataMemberBinding.FirstName property the designer shows it as an error

Cannot Resolve property 'FirstName' in data Context of type object

Is it a bug or is it Resharper playing tricks on me

Sample code:

public class Person 
{
    public string FirstName{
       get { return _firstName; }
       set { SetPropertyValue("FirstName", ref _firstName, value); }
    }
}
public class DataService 
{
    public IDataSource DataContext { get; set; }
    public ICollectionView PersonCollection{ get; set; }

    public DataService()
    {
        DataContext = new DataSource();
        //QueryableCollectionView is from Telerik 
        //but if i use any other CollectionView same thing
        //DataContext Persons is an ObservableCollection<Person> Persons
        PersonCollection = new QueryableCollectionView(DataContext.Persons);
    }
}

<telerik:RadGridView x:Name="ParentGrid" 
    ItemsSource="{Binding DataService.PersonCollection}"
    AutoGenerateColumns="False">
    <telerik:RadGridView.Columns >
        <telerik:GridViewDataColumn Header="{lex:Loc Key=FirstName}"  
            DataMemberBinding="{Binding FirstName}"/>
    </telerik:RadGridView.Columns>
</telerik:RadGridView>

回答1:

The warnings that Resharper is giving you in the XAML view is because the design-time view of the control does not know what type it's data-context is. You can use a d:DesignInstance to help with your bindings.

Add the following (replacing Assembly/Namespace/Binding Target names appropriately)

<UserControl x:Class="MyNamespace.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup‐compatibility/2006"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:lcl="clr‐namespace:MyAssembly"
d:DataContext="{d:DesignInstance Type=lcl:ViewModel}">


回答2:

Your entity has not been transformed in object, it's because the interface ICollectionView is not a generic collection so ReSharper has no way to know that it holds a collection of Person.

You can create a generic version of ICollectionView and use it for your PersonCollection property as demonstrated in this post https://benoitpatra.com/2014/10/12/a-generic-version-of-icollectionview-used-in-a-mvvm-searchable-list/.

First an interface:

public interface ICollectionView<T> : IEnumerable<T>, ICollectionView
{
}

The implementation:

public class GenericCollectionView<T> : ICollectionView<T>, IEditableCollectionView<T>
{
    #region Fields

    [NotNull] readonly ListCollectionView collectionView;

    #endregion

    #region Properties

    public CultureInfo Culture
    {
        get => collectionView.Culture;
        set => collectionView.Culture = value;
    }

    public IEnumerable SourceCollection => collectionView.SourceCollection;

    public Predicate<object> Filter
    {
        get => collectionView.Filter;
        set => collectionView.Filter = value;
    }

    public bool CanFilter => collectionView.CanFilter;

    public SortDescriptionCollection SortDescriptions => collectionView.SortDescriptions;

    public bool CanSort => collectionView.CanSort;

    public bool CanGroup => collectionView.CanGroup;

    public ObservableCollection<GroupDescription> GroupDescriptions => collectionView.GroupDescriptions;

    public ReadOnlyObservableCollection<object> Groups => collectionView.Groups;

    public bool IsEmpty => collectionView.IsEmpty;

    public object CurrentItem => collectionView.CurrentItem;

    public int CurrentPosition => collectionView.CurrentPosition;

    public bool IsCurrentAfterLast => collectionView.IsCurrentAfterLast;

    public bool IsCurrentBeforeFirst => collectionView.IsCurrentBeforeFirst;

    public NewItemPlaceholderPosition NewItemPlaceholderPosition
    {
        get => collectionView.NewItemPlaceholderPosition;
        set => collectionView.NewItemPlaceholderPosition = value;
    }

    public bool CanAddNew => collectionView.CanAddNew;

    public bool IsAddingNew => collectionView.IsAddingNew;

    public object CurrentAddItem => collectionView.CurrentAddItem;

    public bool CanRemove => collectionView.CanRemove;

    public bool CanCancelEdit => collectionView.CanCancelEdit;

    public bool IsEditingItem => collectionView.IsEditingItem;

    public object CurrentEditItem => collectionView.CurrentEditItem;

    #endregion

    #region Events

    public event NotifyCollectionChangedEventHandler CollectionChanged
    {
        add => ((ICollectionView) collectionView).CollectionChanged += value;
        remove => ((ICollectionView) collectionView).CollectionChanged -= value;
    }

    public event CurrentChangingEventHandler CurrentChanging
    {
        add => ((ICollectionView) collectionView).CurrentChanging += value;
        remove => ((ICollectionView) collectionView).CurrentChanging -= value;
    }

    public event EventHandler CurrentChanged
    {
        add => ((ICollectionView) collectionView).CurrentChanged += value;
        remove => ((ICollectionView) collectionView).CurrentChanged -= value;
    }

    #endregion

    #region Constructors

    public GenericCollectionView([NotNull] ListCollectionView collectionView)
    {
        this.collectionView = collectionView ?? throw new ArgumentNullException(nameof(collectionView));
    }

    #endregion

    #region Methods

    public IEnumerator<T> GetEnumerator()
    {
        return (IEnumerator<T>) ((ICollectionView) collectionView).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((ICollectionView) collectionView).GetEnumerator();
    }

    public bool Contains([NotNull] object item)
    {
        return collectionView.Contains(item);
    }

    public void Refresh()
    {
        collectionView.Refresh();
    }

    public IDisposable DeferRefresh()
    {
        return collectionView.DeferRefresh();
    }

    public bool MoveCurrentToFirst()
    {
        return collectionView.MoveCurrentToFirst();
    }

    public bool MoveCurrentToLast()
    {
        return collectionView.MoveCurrentToLast();
    }

    public bool MoveCurrentToNext()
    {
        return collectionView.MoveCurrentToNext();
    }

    public bool MoveCurrentToPrevious()
    {
        return collectionView.MoveCurrentToPrevious();
    }

    public bool MoveCurrentTo(object item)
    {
        return collectionView.MoveCurrentTo(item);
    }

    public bool MoveCurrentToPosition(int position)
    {
        return collectionView.MoveCurrentToPosition(position);
    }

    public object AddNew()
    {
        return collectionView.AddNew();
    }

    public void CommitNew()
    {
        collectionView.CommitNew();
    }

    public void CancelNew()
    {
        collectionView.CancelNew();
    }

    public void RemoveAt(int index)
    {
        collectionView.RemoveAt(index);
    }

    public void Remove([NotNull] object item)
    {
        collectionView.Remove(item);
    }

    public void EditItem(object item)
    {
        collectionView.EditItem(item);
    }

    public void CommitEdit()
    {
        collectionView.CommitEdit();
    }

    public void CancelEdit()
    {
        collectionView.CancelEdit();
    }

    #endregion
}

And finally the usage:

ICollectionView<Person> PersonCollectionView { get; }

In the constructor:

var view = (ListCollectionView) CollectionViewSource.GetDefaultView(PersonCollection);
PersonCollectionView = new GenericCollectionView<Person>(view);


回答3:

Neither d:DataContext="{d:DesignInstance Type=lcl:ViewModel}"> nor GenericCollectionView works directly for a DataGrid with a CollectionViewSource.

    <DataGrid AutoGenerateColumns="False"
              ItemsSource="{Binding collectionViewSource.View}" 
              SelectedItem="{Binding SelectedRow}" 

We can't set "d:DataContext"; because, we often need to bind multiple properties to our viewmodel.

The CollectionViewSource creates new ListCollectionView which is runtime instantiated each time you set the Source property. Since setting the Source property is the only reasonable way to refresh a range of rows, we can't keep a GenericCollectionView around.

My solution is perhaps perfectly obvious, but I dumped the CollectionViewSource. By making creating a property

private ObservableCollection<ListingGridRow> _rowDataStoreAsList;
public GenericCollectionView<ListingGridRow> TypedCollectionView
{
  get => _typedCollectionView;
  set { _typedCollectionView = value; OnPropertyChanged();}
}

public void FullRefresh()
{
    var listData = _model.FetchListingGridRows(onlyListingId: -1);
    _rowDataStoreAsList = new ObservableCollection<ListingGridRow>(listData);
    var oldView = TypedCollectionView;
    var saveSortDescriptions = oldView.SortDescriptions.ToArray();
    var saveFilter = oldView.Filter;
    TypedCollectionView = new GenericCollectionView<ListingGridRow>(new ListCollectionView(_rowDataStoreAsList));
    var newView = TypedCollectionView;
    foreach (var sortDescription in saveSortDescriptions)
    {
        newView.SortDescriptions.Add(new SortDescription()
        {
            Direction = sortDescription.Direction,
            PropertyName = sortDescription.PropertyName
        });
    }
    newView.Filter = saveFilter;
}
internal void EditItem(object arg)
{
    var view = TypedCollectionView;
    var saveCurrentPosition = view.CurrentPosition;
    var originalRow = view.TypedCurrentItem;
    if (originalRow == null)
        return;
    var listingId = originalRow.ListingId;
    var rawListIndex = _rowDataStoreAsList.IndexOf(originalRow);
    // ... ShowDialog ... DialogResult ...
    var lstData = _model.FetchListingGridRows(listingId);
    _rowDataStoreAsList[rawListIndex] = lstData[0];
    view.MoveCurrentToPosition(saveCurrentPosition);
    view.Refresh();
}

After adding public T TypedCurrentItem => (T)collectionView.CurrentItem; To the GenericCollectionView provided by Maxence.