ObservableCollection刷新视图MVVM

时间:2018-09-06 22:32:43

标签: c# wpf listbox

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

问题是列表框的显示成员绑定到一个属性,该属性组合了该项目的两个字段,即数字和日期。 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的聊天,让我重新思考了所有这一切!

1 个答案:

答案 0 :(得分:0)

您可以通过以下两种方法之一解决此问题。您的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>