Home > Net >  WPF: Data binding to common property across views
WPF: Data binding to common property across views

Time:01-11

I have a WPF application, where I want to keep a centralized date selection. I want to allow the date to set through one screen, and update it on others. Below is the common service,

public interface IDateService
{   
    public DateTime ScheduledDate { get; set; }
}

public sealed class DateService :  ObservableObject, IDateService
{
    private DateTime _scheduledDate = DateTime.Now.AddDays(1);

    public DateTime ScheduledDate
    {
        get => _scheduledDate;
        set
        {
            SetProperty(ref _scheduledDate, value);
        }
    }
}

I inject this though the constrictor of the view models of each screen.

   public DateSetViewModel( IDateService dateService, IDialogCoordinator dialogCoordinator)
    {
        _dateService = dateService;

    }

    public DateTime ScheduledDate
    {
        get => _dateService.ScheduledDate;
        set
        {
            _dateService.ScheduledDate = value;
        }
    }

and on read only views


 public class DateReadViewModel : ObservableObject
 {
    private readonly IDateService _dateService;


  public DateReadViewModel( IDateService dateService, IDialogCoordinator dialogCoordinator)
    {
        _dateService = dateService;

    }

    public DateTime ScheduledDate
    {
        get => _dateService.ScheduledDate;
    }

...
}

Now, when loading, all screen shows initial date (now 1 day). Any update made through DateSetViewModel is reflected on that page UI. But, when switch to other views, it always shows initial date, not the updated value from IDateService. I tried to directly bind to dateService.ScheduledDate in other views, but it didn't work. I use MahApps.Metro to define the views if that matters.

The bindings on DateSetView

<DatePicker Width="100"
                            Margin="{StaticResource ControlMargin}"
                            SelectedDate="{Binding ScheduledDate}" />

and other views, I tried few, but similar to

   <DatePicker Width="100"
                    Margin="5"
                    mah:TextBoxHelper.AutoWatermark="True"
                    SelectedDate="{Binding ScheduledDate, Mode=OneWay}" />
                <TextBlock 
                    Margin="5"
                    VerticalAlignment="Center"
                    Text="{Binding ScheduledDate}" 
                />

CodePudding user response:

They all work initially because the binding will fetch the value at the time the views are instantiated. Binding dependency properties (like the Text of the TextBlock and the SelectedDate of the DatePicker) though requires some sort of notification so the Binding(Expression) knows to refetch the (updated) value from the source. While your DateService may be sending a notification, those objects you are exposing the DateTime from (and which is the source for the binding) are not (e.g. the DateReadViewModel or DateSetViewModel). You should make those classes provide a notification using one of the mechanisms documented - see the Binding Sources and Targets section of this page for a description of those mechanisms. Basically make ScheduledDate a dependency property, implement a ScheduledDateChanged event -or- implement INotifyPropertyChanged. Or I suppose you could expose the Service itself from the view model but that to me is exposing an internal implementation detail and should be avoided.

CodePudding user response:

DateService is the only object that raises the PropertyChanged event when its ScheduledDate property is set.

Given your current implementation, you need to subscribe to this event in the view models and raise the event for each data-bound source property:

public class DateReadViewModel : ObservableObject
{
    private readonly IDateService _dateService;

    public DateReadViewModel(IDateService dateService, IDialogCoordinator dialogCoordinator)
    {
        _dateService = dateService;
        _dateService.PropertyChanged  = OnDateServicePropertyChanged;
    }

    private void OnDateServicePropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(IDateService.ScheduledDate))
            OnPropertyChanged(nameof(ScheduledDate));
    }

    public DateTime ScheduledDate
    {
        get => _dateService.ScheduledDate;
    }

    ...
}

public interface IDateService : INotifyPropertyChanged
{
    public DateTime ScheduledDate { get; set; }
}

The other option is to bind directly to the property of the service.

  • Related