ObservableCollection刷新视图MVVM

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ObservableCollection刷新视图MVVM相关的知识,希望对你有一定的参考价值。

我有一个绑定到ListBox的ObservableCollection。选择列表框中的项目会根据所选项目使用自己的viewmodel填充用户控件。我正在使用Linq to SQL DataContext从我的模型中获取数据到viewmodels。

问题是列表框的显示成员绑定到一个属性,该属性组合了项目的两个字段,数字和日期。 usercontrol允许用户更改日期,我希望它立即反映在列表框中。

我初始化集合并添加CollectionChanged和PropertyChanged处理程序,以便集合正在侦听集合中属性的更改:

public void FillReports()
{
    if (oRpt != null) oRpt.Clear();
    _oRpt = new ViewableCollection<Reportinformation>();
    //oRpt.CollectionChanged += CollectionChanged; //<--Don't need this
    foreach (Reportinformation rpt in _dataDc.Reportinformations.Where(x => x.ProjectID == CurrentPrj.ID).OrderByDescending(x => x.Reportnumber))
    {
        oRpt.Add(rpt);
    }
}

private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    if (e != null)
    {
        if (e.OldItems != null)
        {
            foreach (INotifyPropertyChanged rpt in e.OldItems)
            {
                rpt.PropertyChanged -= item_PropertyChanged;
            }
        }
        if (e.NewItems != null)
        {
            foreach (INotifyPropertyChanged rpt in e.NewItems)
            {
                rpt.PropertyChanged += item_PropertyChanged;
            }
        }
    }
}

private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    string s = sender.GetType().ToString();
    if(s.Contains("Reportinformation"))
        RaisePropertyChangedEvent("oRpt"); //This line does get called when I change the date
    else if (s.Contains("Observation"))
    {
        RaisePropertyChangedEvent("oObs");
        RaisePropertyChangedEvent("oObsByDiv");
    }
}

日期会正确更改并且更改仍然存在并写回数据库,但更改不会反映在列表框中,除非我实际更改了集合(当我在与列表框相同的窗口中的另一个控件上切换作业时会发生这种情况) 。我的属性更改处理程序中的行引发了“oRpt”的更改事件,这是绑定到ListBox的可观察集合,更改日期会调用处理程序,并使用调试器进行验证:

    <ListBox x:Name="lsbReports" ItemsSource="{Binding oRpt}" DisplayMemberPath="ReportLabel" SelectedItem="{Binding CurrentRpt}" 
            Grid.Row="1" Grid.Column="0" Height="170" VerticalAlignment="Bottom" BorderBrush="{x:Null}" Margin="0,0,5,0"/>

但似乎只是提出这种改变实际上并没有触发视图刷新列表框中项目的“名称”。我也试图为绑定到DisplayMemberPath的ReportLabel提升,但这不起作用(值得一试)。我不知道从哪里开始,因为我认为根据改变其中一个实际项目的日期(因此名称)来重新加载oRpt集合是不好的做法,因为我希望这个数据库能够相当快地增长。

这是Reportinformation扩展类(这是一个自动生成的LinqToSQL类,所以我的部分如下):

public partial class Reportinformation // : ViewModelBase <-- take this out INPC already hooked up
{
    public ViewableCollection<Person> lNamesPresent { get; set; }
    public string ShortDate
    {
        get
        {
            DateTime d = (DateTime)Reportdate;
            return d.ToShortDateString();
        }
        set
        {
            DateTime d = DateTime.Parse(value);
            if (d != Reportdate)
            {
                Reportdate = DateTime.Parse(d.ToShortDateString());
                SendPropertyChanged("ShortDate");//This works and uses the LinqToSQL call not my ViewModelBase call
                SendPropertyChanged("ReportLabel"); //use the LinqToSQL call
                 //RaisePropertyChangedEvent("ReportLabel"); //<--This doesn't work
            }
        }
    }

    public string ReportLabel
    {
        get
        {
            return string.Format("{0} - {1}", Reportnumber, ShortDate);
        }
    }

    public void Refresh()
    {
        RaisePropertyChangedEvent("oRpt");
    }

    public string RolledNamesString
    {
        get
        {
            if (lNamesPresent == null) return null;
            return string.Join("|",lNamesPresent.Where(x=>x.Name!= "Present on Site Walk").Select(x=>x.Name).ToArray());
        }
    }
}

回答

所以我的错误在于我正在添加LinqToSQL部分类,并且正在使用我的ViewModelBase,它在自动生成的分部类之上重新实现所有INPC内容。我解除了这一点,只使用自动生成的设计器中的INPC,它都按预期工作。感谢SledgeHammer的聊天,让我重新思考这一切!

答案

你可以用两种方法解决这个问题。您的ReportInformation类需要实现INotifyPropertyChanged并在ReportLabel属性更改时提升属性更改事件:

public class ReportInformation : INotifyPropertyChanged
{
    private int _numberField;
    private DateTime _dateField;

    public int NumberField
    {
        get => _numberField;
        set 
        {
            if (_numberField != value)
            {
                _numberField = value;
                RaisePropertyChanged();
                RaisePropertyChanged(nameof(ReportLabel));
            }
        }
    }

    public DateTime DateField
    {
        get => _dateField;
        set
        {
            if (_dateField != value)
            {
                _dateField = value;
                RaisePropertyChanged();
                RaisePropertyChanged(nameof(ReportLabel));
            }
        }
    }

    public string ReportLabel => $"{NumberField}: {DateField}";

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void RaisePropertyChanged([CallerMemberName]string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

或者,你可以在你的ListBox使用ItemTemplate而不是像DisplayMemberPath这样:

<ListBox x:Name="lsbReports" 
         ItemsSource="{Binding oRpt}"
         SelectedItem="{Binding CurrentRpt}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding NumberField}"/>
                <TextBlock Text=": "/>
                <TextBlock Text="{Binding DateField}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

以上是关于ObservableCollection刷新视图MVVM的主要内容,如果未能解决你的问题,请参考以下文章

当ObservableCollection发生变化时更新CollectionViewSource WP7

修改 ItemsSource ObservableCollection 后如何刷新组合框

有没有办法在不通知 ItemsSource 集合的情况下使用 ObservableCollection 和 MVVM 刷新 WPF DataGrid?

wpf中,当DataGrid.ItemsSource与ObservableCollection绑定后,值变化时,DataGrid如何刷新?

C#编程(五十六)----------可观察的集合ObservableCollection

Listview 不会根据 ObservableCollection 属性进行更新