使用 CollectionViewSource 实现 ListView - 不刷新?

发布于 2024-12-22 09:46:11 字数 1802 浏览 4 评论 0原文

我正在设置一个 ListView,其 Source 属性设置为我的一个类的 ivar,称为 Cat

每个 Cat 都有一个 Trait 对象的 ObservableCollection

private ObservableCollection<Trait> _traits = new ObservableCollection<Trait>();

public ObservableCollection<Trait> Traits
{
get
    {
        return _traits;
    }
}

public void AddTrait(Trait t)
{
    _traits.Add(t);
    // Is this redundant? Is one better than the other?
    this.OnPropertyChanged("_traits");
    this.OnPropertyChanged("Traits");
}

public IEnumerator<Object> GetEnumerator()
{
    return _traits.GetEnumerator();
}

然后我将 Source 属性分配给这个 >Traits 集合:

this.CollectionViewSource.Source = CurrentCat.Traits;

这可以正常工作,并且 Trait 对象可以正确显示在我的 ListView 中。

问题是,对此基础 _traits 集合的更改不会导致 UI 正确更新。例如,这样:

void AddTraitButton_Click(object sender, RoutedEventArgs e)
{
    if (this.CurrentCat != null)
    {
        this.CurrentCat.AddTrait(new Trait());
    }
}

在 UI 中似乎不会立即产生任何效果,但是如果我像这样重置 Source 属性:

var oldSource = this.CollectionViewSource.Source;
this.CollectionViewSource.Source = null;
this.CollectionViewSource.Source = oldSource;

那么 ListView 正确更新。但是,我确信一定有一些东西是我遗漏的,因为我希望 UI 在添加/删除项目时进行更新。

编辑: CollectionViewSource 正在应用于我的 XAML 文件中的 ListView

<CollectionViewSource x:Name="CollectionViewSource" x:Key="CollectionViewSource" />

...

<ListView x:Name="ItemListView" ItemsSource="{Binding Source={StaticResource CollectionViewSource}}" ...

I'm working on setting up a ListView whose Source property is set to an ivar of a class of mine, called Cat.

Each Cat has an ObservableCollection of Trait objects:

private ObservableCollection<Trait> _traits = new ObservableCollection<Trait>();

public ObservableCollection<Trait> Traits
{
get
    {
        return _traits;
    }
}

public void AddTrait(Trait t)
{
    _traits.Add(t);
    // Is this redundant? Is one better than the other?
    this.OnPropertyChanged("_traits");
    this.OnPropertyChanged("Traits");
}

public IEnumerator<Object> GetEnumerator()
{
    return _traits.GetEnumerator();
}

And then I'm assigning the Source property to this Traits collection:

this.CollectionViewSource.Source = CurrentCat.Traits;

This works properly, and the Trait objects are properly displayed in my ListView.

The issue is that changes to this underlying _traits collection do not cause the UI to update properly. For example, this:

void AddTraitButton_Click(object sender, RoutedEventArgs e)
{
    if (this.CurrentCat != null)
    {
        this.CurrentCat.AddTrait(new Trait());
    }
}

Doesn't seem to have any effect immediately in the UI, but if I reset the Source property like so:

var oldSource = this.CollectionViewSource.Source;
this.CollectionViewSource.Source = null;
this.CollectionViewSource.Source = oldSource;

Then the ListView updates properly. But, I'm sure there must be something that I'm missing, as I'd like for the UI to update upon the addition/removal of an item.

Edit: The CollectionViewSource is being applied to the ListView in my XAML file:

<CollectionViewSource x:Name="CollectionViewSource" x:Key="CollectionViewSource" />

...

<ListView x:Name="ItemListView" ItemsSource="{Binding Source={StaticResource CollectionViewSource}}" ...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

烟织青萝梦 2024-12-29 09:46:11

我现在似乎找不到它,但我似乎记得绑定到 CollectionViewSource 时遇到一些问题。您是否尝试过直接绑定到 CurrentCat.Traits 并在代码隐藏中设置 this.DataContext = this (我假设您在这里没有使用 MVVM)?

<ListView x:Name="ItemListView" ItemsSource="{Binding CurrentCat.Traits}" />

I can't seem to find it now, but I seem to remember some problem with binding to CollectionViewSource. Have you tried binding directly to CurrentCat.Traits and setting this.DataContext = this in the code-behind (I am assuming you aren't using MVVM here)?

<ListView x:Name="ItemListView" ItemsSource="{Binding CurrentCat.Traits}" />
醉酒的小男人 2024-12-29 09:46:11

我相信您不想直接绑定到 CollectionViewSource 并替换其 Source 来强制刷新,而是希望绑定到 CVS 的 View 属性...

<ListView x:Name="ItemListView" 
          ItemsSource="{Binding Source={StaticResource CollectionViewSource}, Path=View}" ...

...并调用 <更新源集合后,code>CollectionViewSource.Refresh()。

void AddTraitButton_Click(object sender, RoutedEventArgs e)
{
    if (this.CurrentCat != null)
    {
        this.CurrentCat.AddTrait(new Trait());
        this.CollectionViewSource.Refresh();
    }
}

另外,有几点需要注意,因为您似乎对 .NET/WPF 约定相对较新:

  • 私有成员通常称为“字段”而不是“ivars”(Objective-C 背景?:))
  • .NET 类的 与 this 关键字通常是多余的,除非范围内有另一个具有相同名称的标识符
  • 值得探索 MVVM 和相关模式(如果您要在 WPF 中执行任何重要操作);它们帮助您使视图(XAML 对象)尽可能轻便且易于更改。

    例如,就您的情况而言,我假设您显示的代码来自包含您的 ListView 的任何 Window 或 UserControl 的隐藏代码。遵循 MVVM 模式将涉及创建一个单独的“ViewModel”类,该类将包含 Traits 集合并通过 CollectionViewSource 公开它(使用 View 属性,正如我所提到的) )。然后,您的 UserControl 将分配一个 ViewModel 实例作为其 DataContext,并且 ListView 可以绑定到公开的 CollectionView。

Rather than binding to the CollectionViewSource directly and replacing its Source to force a refresh, I believe you want to bind to the CVS's View property...

<ListView x:Name="ItemListView" 
          ItemsSource="{Binding Source={StaticResource CollectionViewSource}, Path=View}" ...

...and call CollectionViewSource.Refresh() after updating the source collection.

void AddTraitButton_Click(object sender, RoutedEventArgs e)
{
    if (this.CurrentCat != null)
    {
        this.CurrentCat.AddTrait(new Trait());
        this.CollectionViewSource.Refresh();
    }
}

Also, a couple notes, since you seem relatively new to .NET/WPF conventions:

  • The private members of .NET classes are typically referred to as "fields" rather than "ivars" (Objective-C background? :))
  • Prefixing class members with the this keyword is usually redundant, unless there is another identifier in scope with the same name
  • It's worth exploring the MVVM and related patterns if you'll be doing anything non-trivial in WPF; they help you keep your views (XAML objects) as light and easy-to-change as possible.

    In your case, for example, I assume the code you've shown is from the code-behind of whatever Window or UserControl contains your ListView. Following the MVVM pattern would involve creating a separate "ViewModel" class that would contain the Traits collection and expose it via a CollectionViewSource (using the View property, as I've mentioned). Your UserControl would then have an instance of the ViewModel assigned as its DataContext, and the ListView could be bound to the exposed CollectionView.

洒一地阳光 2024-12-29 09:46:11

您仍然可以专门使用 ObservableCollection。 尽管存在一个问题 - 它不会在 IsInDesignMode 中显示数据。也许将来会有所改善。

public class MainViewModel : ViewModelBase
{
...
    private ObservableCollection<PartViewModel> _parts;
    public ObservableCollection<PartViewModel> Parts
    {
        get
        {
            if (_parts == null)
            {
                _parts = new ObservableCollection<PartViewModel>();
                _parts.CollectionChanged += _parts_CollectionChanged;
            }
            return _parts;
        }
    }

    object m_ReorderItem;
    int m_ReorderIndexFrom;
    void _parts_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Remove:
                m_ReorderItem = e.OldItems[0];
                m_ReorderIndexFrom = e.OldStartingIndex;
                break;
            case NotifyCollectionChangedAction.Add:
                if (m_ReorderItem == null)
                    return;
                var _ReorderIndexTo = e.NewStartingIndex;
                m_ReorderItem = null;
                break;
        }
    }

    private PartViewModel _selectedItem;
    public PartViewModel SelectedItem
    {
        get
        {
            return _selectedItem;
        }
        set
        {
            if (_selectedItem != value)
            {
                _selectedItem = value;
                RaisePropertyChanged("SelectedItem");
            }
        }
    }
   ...

    #region ViewModelBase

    public override void Cleanup()
    {
        if (_parts != null)
        {
            _parts.CollectionChanged -= _parts_CollectionChanged;
        }
        base.Cleanup();
    }

    #endregion

  }

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Grid.Resources>
        <CollectionViewSource x:Name="PartsCollection" Source="{Binding Parts}"/>
    </Grid.Resources>

    <ListView Margin="20" CanReorderItems="True" CanDragItems="True" AllowDrop="True" 
              ItemsSource="{Binding Source={StaticResource PartsCollection}}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" SelectionMode="Single">
        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
            </Style>
        </ListView.ItemContainerStyle>
        <ListView.ItemTemplate>
        ...
        </ListView.ItemTemplate>
    </ListView>
</Grid>

You may work exclusively with the ObservableCollection still. Although there is one problem - it would not show the data in IsInDesignMode. Maybe in the future it will improve.

public class MainViewModel : ViewModelBase
{
...
    private ObservableCollection<PartViewModel> _parts;
    public ObservableCollection<PartViewModel> Parts
    {
        get
        {
            if (_parts == null)
            {
                _parts = new ObservableCollection<PartViewModel>();
                _parts.CollectionChanged += _parts_CollectionChanged;
            }
            return _parts;
        }
    }

    object m_ReorderItem;
    int m_ReorderIndexFrom;
    void _parts_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Remove:
                m_ReorderItem = e.OldItems[0];
                m_ReorderIndexFrom = e.OldStartingIndex;
                break;
            case NotifyCollectionChangedAction.Add:
                if (m_ReorderItem == null)
                    return;
                var _ReorderIndexTo = e.NewStartingIndex;
                m_ReorderItem = null;
                break;
        }
    }

    private PartViewModel _selectedItem;
    public PartViewModel SelectedItem
    {
        get
        {
            return _selectedItem;
        }
        set
        {
            if (_selectedItem != value)
            {
                _selectedItem = value;
                RaisePropertyChanged("SelectedItem");
            }
        }
    }
   ...

    #region ViewModelBase

    public override void Cleanup()
    {
        if (_parts != null)
        {
            _parts.CollectionChanged -= _parts_CollectionChanged;
        }
        base.Cleanup();
    }

    #endregion

  }

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Grid.Resources>
        <CollectionViewSource x:Name="PartsCollection" Source="{Binding Parts}"/>
    </Grid.Resources>

    <ListView Margin="20" CanReorderItems="True" CanDragItems="True" AllowDrop="True" 
              ItemsSource="{Binding Source={StaticResource PartsCollection}}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" SelectionMode="Single">
        <ListView.ItemContainerStyle>
            <Style TargetType="ListViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
            </Style>
        </ListView.ItemContainerStyle>
        <ListView.ItemTemplate>
        ...
        </ListView.ItemTemplate>
    </ListView>
</Grid>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文