2013-07-18 182 views
0

我在我的WPF/MVVM(带MVVM Light框架)游戏中绑定了一个TextBlock游戏,该游戏绑定到一个属性,该属性应该反映雇用员工的数量。我已确认绑定完好无损,但无法更新。WPF中的绑定属性不更新

这里是在我看来,TextBlock

<TextBlock x:Name="WorkersTextBlock" 
      FontFamily="Pericles" 
      DataContext="{Binding Guilds[0]}" 
      Text="{Binding Workers.Count, 
          StringFormat=Workers : {0}, 
          FallbackValue=Workers : 99}" /> 

在我的视图模型属性:

public ObservableCollection<Guild> Guilds 
{ 
    get { return DataManager.Data.Guilds; } 
} 

而且在我的视图模型,该命令更改Worker的雇主财产:

private void ExecuteHireWorkerCommand() 
{ 
    if (SelectedWorker == null) 
     return; 

    SelectedWorker.Employer = DataManager.Data.Guilds[0]; 
    Gold -= SelectedWorker.Salary; 
    _workerCollectionView.Refresh(); 
} 

在DataManager中,它是一个包含我所有数据的单例类:

private ObservableCollection<Guild> _guilds = new ObservableCollection<Guild>(); 
public ObservableCollection<Guild> Guilds 
{ 
    get { return _guilds; } 
} 

private ObservableCollection<Worker> _workers = new ObservableCollection<Worker>(); 
public ObservableCollection<Worker> Workers 
{ 
    get { return _workers; } 
} 

Guild模式:

public ObservableCollection<Worker> Workers 
{ 
    get { return DataManager.Data.Workers.Where(w => w.Employer == this).ToObservableCollection(); } 
} 

Worker雇主属性是:

public Guild Employer { get; set; } 

而在去年,我的扩展方法(我认为这是问题的根源):

public static ObservableCollection<T> ToObservableCollection<T>(this IEnumerable<T> source) 
{ 
    if (source == null) 
    { 
     throw new ArgumentNullException("source"); 
    } 
    return new ObservableCollection<T>(source); 
} 

消息框确认通过命令Worker s“雇主”属性正在更新,但我没有试过任何东西使得TextBlock更新。我已经尝试在我在这里列出的一切都没有运气的情况下实施RaisePropertyChanged

如果我在初始化数据时将构造函数中的Worker的Employment属性设置为正确的公会,则TextBlock中的数字将正确显示,但在此之后不会更新。我的直觉是Workers属性中的LINQ过滤和扩展方法导致了这个麻烦,但我可能是错的。

如果任何人有任何想法如何使这个工作,我很乐意听到他们。任何有关此事的建议都将不胜感激。如果您需要更多的代码或信息,请询问。

谢谢。

更新:我认为罗恩是在正确的道路上;扩展方法可能会破坏绑定。如果是这样的话,任何人都可以给我任何建议,如何在不破坏绑定的情况下过滤Guild中的Workers属性?另外,就setter问题而言,我向Workers属性添加了一个setter,但它实际上从未实际触发。

+0

这个神奇的可能是“我已经尝试在我没有运气列出的所有事情上实现RaisePropertyChanged”向我们展示该代码。我的猜测是你在那里犯了一些错误。 –

+0

我用它来通知:http://kindofmagic.codeplex.com/这很难搞砸了,它完美适用于其他属性。 –

+1

我几乎没有见过更糟糕的命名属性。它应该被称为NotifiesWhenPropertyChangesAttribute。魔术可以是任何东西。 –

回答

1

我认为你需要重新设计底层数据结构。但尽管如此,你可以稍微改变它以使其工作。

更改Workers属性ICollectionView像这样:

public ICollectionView Workers { get; set; }

然后在公会模型的contstructor您可以从您的数据管理器填充工人集合,像这样:

Workers = CollectionViewSource.GetDefaultView(DataManager.Data.Workers);

并添加一个过滤器到你的ICollectionView像这样:

Workers.Filter = (worker) => { return (worker.Employer == this); };

并且每当Workers集合被更新时调用Workers.Refresh()

这样你的绑定不会中断,你的Workers集合将保留相同的实例。

哦,并将UpdateSourceTrigger=PropertyChanged添加到您的TextBox绑定。

就像我说过的,我会着重于完全重新设计支持数据结构,但不知道为什么或如何实现它,我不能说更多。

+0

我刚试过这种方法,并得到它的工作,它应该做得很好。谢谢你的帮助。就支持数据而言,它全部存储在单一类中用于全局访问。这是我采取的第一个真正的项目,我很缺乏经验,所以我真的不知道有更好的方法去做。它目前包含的所有内容都是4个'ObservableCollection',这是我正在处理的每个主要类型的一个主列表。如果你能提出一个更好的方法去做,我很乐意去学习它。我知道我在做什么并不完美。 –

+0

如果我没有全部要求,那么很难告诉你如何改进支持数据的结构,但是我会为'Model'本身的每个类型保留一个私有的静态'ObservableCollection'。这样你就不必重定向到数据管理器,一切都包含在一个类中。所以'公会'将成为'模型',并公开静态字段/属性'ObservableCollection Guilds'。 ANYWAY,这是我会这样做的一种方式。 – Xtr

+0

我不确定我完全理解。也许我误解了MVVM中模型的概念,但现在我有四种主要类型的模型。每个模型只包含所需的公共属性和初始化数据的方法。每种类型都有多个实例,所以我使用单例类来将它们全部放在一个中心位置,因此我们使用'ObservableCollection '。我不确定在模型中包含'ObservableCollection'如何工作,或者它如何更有效。 –

1
public ObservableCollection<Worker> Workers 
{ 
    get { return DataManager.Data.Workers.Where(w => w.Employer == this).ToObservableCollection(); } 
} 

当此属性实际发生更改时,您不通知绑定系统。如果底层的集合本身发生了变化,你会没事的。但是你甚至没有保留对这个底层集合的引用 - 你只是将它返回。

正常模式会是这样的(假设你在Guild实施INotifyPropertyChanged

private ObservableCollection<Worker> _Workers; 
public ObservableCollection<Worker> Workers 
{ 
    get { return _Workers; } 
    set 
    { 
     if (value != _Workers) 
     { 
      _Workers = value; 
      NotifyPropertyChanged("Workers") 
     } 
    } 
} 

但它一定程度上取决于你是如何你的对象设立。无论如何,你需要通知系统该集合以某种方式改变。

编辑:你在评论中提到你使用魔法种类。我去阅读文档。它说如何工作,它说

5)显式或隐式地应用MagicAttribute转换公共属性的所有可用setter。

您没有该属性的setter,所以它不会修复它。

+0

将一个setter添加到'Workers'属性并在'DataManager'中添加Notify属性不会改变任何内容。就“工人”财产而言,我不能在setter中使用LINQ筛选。我设法通过使用CollectionView绑定到DataGrid时解决了这个问题,但我不知道在这种情况下该怎么做。 –

+0

我在'Guild'中的Workers属性中添加了一个占位符setter,但即使'Worker'的Employer属性中的setter也不会触发。 –

+0

我认为这里的底线是如果您依靠LINQ筛选来创建属性,那么您将无法执行绑定。如果我是你,我会考虑设置一个ViewModel,将你的模型的部分(公会,所选公会的工作人员等)展示给视图。它需要一点点设置,但它会减轻你的绑定困境一吨。 – Tim

0

我没有测试你的代码,但在Guild模型,返回new ObservableCollection(可以在您的扩展方法看到的)可能打破你的绑定,我建议一个小的重新设计,以便绑定的观点是始终与ObservableCollection的原始实例关联。尽管我没有使用你的框架,当我实现MVVM模式时,我总是确保我的ViewModel的observables保持相同的实例,并且我使用Clear方法来替换我的OnModelChanged方法中的内容,即方式您不必处理通知需要通知这种变化的观点,并且由您的ObservableCollection处理。

+0

那肯定是理想的。 LINQ过滤返回一个IEnumerable,所以这就是扩展方法的用处。但我相信你是正确的,并且返回一个新对象就是在这里搞砸了什么。你知道我怎么能够在属性中使用某种过滤器而不破坏绑定? –

+0

我建议你将这两个可观察的集合分开:拥有一个始终拥有相关工作人员的集合(需要时填充它 - > OnModelChanged),视图将绑定到该集合。这个集合将只实例化一次(如果你没有绑定到'旧'集合,不妨将它作为一个常规列表) –