I have a TextBlock
in my WPF/MVVM (with MVVM Light framework) game that is bound to a property that is supposed to reflect the number of employed workers. I have confirmed that the binding is intact, but I can't get it to update.
Here's the TextBlock
in my view:
<TextBlock x:Name="WorkersTextBlock"
FontFamily="Pericles"
DataContext="{Binding Guilds[0]}"
Text="{Binding Workers.Count,
StringFormat=Workers : {0},
FallbackValue=Workers : 99}" />
The property in my viewmodel:
public ObservableCollection<Guild> Guilds
{
get { return DataManager.Data.Guilds; }
}
Also in my viewmodel, the command to change a Worker
's Employer property:
private void ExecuteHireWorkerCommand()
{
if (SelectedWorker == null)
return;
SelectedWorker.Employer = DataManager.Data.Guilds[0];
Gold -= SelectedWorker.Salary;
_workerCollectionView.Refresh();
}
In DataManager, which is a singleton class that holds all of my data:
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; }
}
In the Guild
model:
public ObservableCollection<Worker> Workers
{
get { return DataManager.Data.Workers.Where(w => w.Employer == this).ToObservableCollection(); }
}
The Employer property in Worker
is:
public Guild Employer { get; set; }
And last, my extension method (which I believe is the source of the problem):
public static ObservableCollection<T> ToObservableCollection<T>(this IEnumerable<T> source)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
return new ObservableCollection<T>(source);
}
Messageboxes confirm that via the command, Worker
s' Employer properties are being updated properly but nothing I've tried makes the TextBlock
update. I've tried implementing RaisePropertyChanged
on everything I've listed here with no luck.
If I set the Employment property of a Worker
to the correct guild within the constructor when the data is initialized, the number in the TextBlock
displays correctly, but doesn't update after that. My hunch is that the LINQ filtering and extension methods in the Workers
property are causing this trouble, but I could be wrong.
If anybody has any ideas on how to get this to work, I'd love to hear them. Any advice at all on this matter would be greatly appreciated. If you need more code or information, please ask.
Thanks.
Update: I think that Ron is on the right path; the extension method may be breaking the binding. If this is the case, can anybody give me any advice on how to filter the Workers property in Guild
without breaking the binding? Also, as far as the setter issue goes, I added a setter to the Workers property but it never actually fires.
public ObservableCollection<Worker> Workers
{
get { return DataManager.Data.Workers.Where(w => w.Employer == this).ToObservableCollection(); }
}
You're not notifying the binding system when this property actually changes. if the underlying collection itself changed, you'd be fine. But you're not even keeping a reference to that underlying collection - you're just returning it.
The normal pattern would be something like (assuming you're implementing INotifyPropertyChanged
in Guild
)
private ObservableCollection<Worker> _Workers;
public ObservableCollection<Worker> Workers
{
get { return _Workers; }
set
{
if (value != _Workers)
{
_Workers = value;
NotifyPropertyChanged("Workers")
}
}
}
But it somewhat depends on how you have your objects set up. Regardless, you need to notify the system that the collection is changing somehow.
Edit: You mention in the comments that you use Kind of Magic. I went and read the documentation. under how it works, it says
5) Transforms all available setters of public properties with MagicAttribute explicitly or implicitly applied.
You don't have a setter on that property, so it is not going to fix it up.
I think you need to redesign the underlying data structure a bit. But be that as it may, you could change it slightly to make it work.
Change the Workers
property to ICollectionView
like so:
public ICollectionView Workers { get; set; }
Then in the Guild model's contstructor you can populate the workers collection from your data manager like so:
Workers = CollectionViewSource.GetDefaultView(DataManager.Data.Workers);
and add a filter to your ICollectionView
like so:
Workers.Filter = (worker) => { return (worker.Employer == this); };
and call Workers.Refresh()
whenever the Workers
collection is updated.
That way your binding won't break and your Workers
collection will retain the same instance.
Oh, and add the UpdateSourceTrigger=PropertyChanged
to your TextBox
binding.
Like I said, I would look at redesigning the backing data structure entirely, but without knowing why or how you implemented it I cannot say more than that.
I haven't tested your code, but in the Guild
Model, returning a new ObservableCollection
(can be seen in your extension method) might break your binding , I suggest a little redesign in order for the binded view to be always associated with the original instance of the ObservableCollection
.
Although I'm not using your framework, when I implement the MVVM pattern, I always make sure my ViewModel's observables remain the same instance, and I use the Clear
method in order to replace their content in my OnModelChanged
method, that way you don't have to handle notifications needed to alert the view of such changes and it is handled by your ObservableCollection
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.