简体   繁体   中英

Bound property in WPF doesn't update

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM