3

I have a WPF view which contains 2 mutually dependent properties; a change in one might trigger a change in the other. How do I write code that cleanly updates both?

More specifically: I have WPF view which makes it possible to export data. That view contains a list of "channels" (that's how I call my data), and for each channel the user can select different file formats for the export. So it is possible to export channel1 to .dat and .csv, while channel2 is being exported to .xls and .csv, whatever the user selects.

The view also contains a checkbox which indicates whether the channel should be exported at all.

My desired behaviour is:

  • If all file formats are unselected, the main checkbox should be unset.
  • If any file format is selected, the main checkbox is set.
  • If the main checkbox is unset, the list of file formats is unchanged.
  • If the main checkbox is set and there is a list of selected formats, it is unchanged.
  • if the main checkbox is set and the selected formats list is empty, some default file formats are set.

I don't want to throw RaisePropertyChanged everywhere, and would like to have somewhat centralized place to deal with the update.

Naive solution:

public bool? IsSelected {
    get { return this.isSelected; }
    set {
        if (this.isSelected != value) {
            this.isSelected = value;
            if (value == true && this.SelectedFileFormats.Count == 0) {
                ApplyDefaultFileFormats();
            }
            this.RaisePropertyChanged("IsSelected");
        }
    }
}


public void ApplyDefaultFileFormats() {
    this.SelectedFileFormats = // Some logic.
}

public IList<VariableCommandHandler> SelectedFileFormats {
    get { return this.selectedFileFormats; }
    set {
        this.selectedFileFormats = value;
        this.UpdatePropertiesRelatedToSelectedHandlers();
        this.RaisePropertyChanged("SelectedFileFormats");
        this.RaisePropertyChanged("FileName");
    }
}

private void UpdatePropertiesRelatedToSelectedHandlers() {
    this.IsSelected = (this.selectedFileFormats.Count > 0);
    // Do a bunch of other updates, which are derived only from the SelectedHandlers.
}

This works, but I don't like the fact that it is going twice to the IsSelected setter and just misses on going into an infinite loop. Basically, if IsSelected is set to true (while there are no selected file formats), all of the methods above will be invoked. Once in UpdatePropertiesRelatedToSelectedHandlers the execution will go back to IsSelected with true while it is already true, so the if-statement right in the beginning will be true != true and the execution will start to return.

I would like a way that is more straightforward, without calling a setter while being in another execution of that same setter.

I have thought of setting the backing fields in the two setters, and then calling RaisePropertyChanged for both properties from an update method. But this is not the cleanest coding as the setter should be the only place where property changes are being raised.

Any suggestions on a cleaner way to achieve this?

1 Answer 1

0

Circular dependencies like that are a bad idea in any type of software. The solution is to update the UI explicitly when the values change, using a functional approach: take the specification you have, and perform a mapping from the object state to the visual state, every time a dependent property changes, or at appropriate intervals.

To prevent circular dependencies, keep a boolean field (_updatingView) in the higher level view or controller component (the "form" or controller, whatever you use), that indicates you are updating the view. Set this field in a try block, perform the view mapping, then clear the field in the finally block. If the field is set when a UI component changes a relevant property, ignore the change.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.