WPF Data binding один ко многим
Есть DTO IOption класс ObjectGroupOptions. Который может меняться со временем из-за изменения файла конфигурации.
Стоит задача слушать изменения DTO IOption в разных ViewModel.
Из очевидных пришли следующие решения:
- Pub/Sub. Подписываться на изменения в DTO IOption классе в нужных ViewModel. И после в самих ViewModel через рефлексию и аттрибутов устанавливать полученное значение.:
private void OnOptionChange(string optionName, string propertyName, object value)
{
var property = GetType().GetProperties()
.FirstOrDefault(property =>
string.Equals(property.Name, propertyName, StringComparison.CurrentCultureIgnoreCase) &&
property.GetCustomAttribute<BindToOptionAttribute>() is not null &&
string.Equals(
property.GetCustomAttribute<BindToOptionAttribute>()?.optionPropertyName,
optionName, StringComparison.CurrentCultureIgnoreCase));
property?.SetValue(this, value);
}
Но решил отложить подобное решение на крайний случай.
Из еще одного решения - Rx/ReactiveUI. Но решил отказаться от подобного решения, так как тянет множество за собой кода, в такой небольшой части проекта.
И после поиска было найдено интересно решение в виде Bindings в WPF. Что позволяет сделать так:
public class ObjectGroupOptions : FrameworkElement
{
private static readonly Type ThisType = typeof(ObjectGroupOptions);
public static readonly DependencyProperty FirstObjectEnableProperty =
DependencyProperty.Register(
nameof(FirstObjectEnable), typeof(bool),
ThisType, new PropertyMetadata(default(bool)));
public bool FirstObjectEnable
{
get => (bool)GetValue(FirstObjectEnableProperty);
set => SetValue(FirstObjectEnableProperty, value);
}
}
ObjectGroupOptions наследует FrameworkElement, чтобы было удобнее делать Binding в самом классе. И в конструкторе ViewModel можно сделать так
public class FirstViewModel
{
private readonly ObjectGroupOptions old;
public bool FirstObjectEnable {get; set;}
public FirstViewModel(IOptionsMonitor<ObjectGroupOptions> optionsMonitor)
{
this.optionsMonitor.OnChange(Listener);
old = optionsMonitor.CurrentValue;
var bingind = new Binding(nameof(FirstObjectEnable)) { Source = this, Mode = BindingMode.TwoWay };
}
private void Listener(ObjectGroupOptions arg1, string? arg2)
{
BindingOperations.ClearBinding(old, ObjectGroupOptions.FirstObjectEnableProperty);
var bingind = new Binding(nameof(FirstObjectEnable)) { Source = this, Mode = BindingMode.TwoWay };
arg1.SetBinding(ObjectGroupOptions.FirstObjectEnableProperty, bingind);
}
}
Это отлично будет работать, до тех пор, пока не понадобится добавить в другой ViewModel, еще один Binding к такому же DependencyProperty. Или же если View будет устанавливать новое значение для ObjectGroupOptions из-за чего Binding меняется.
public partial class ObjectsGroupSettings
{
private readonly IOptionsMonitor<ObjectGroupOptions> objectGroupOptionsMonitor;
public ObjectsGroupSettings(IOptionsMonitor<ObjectGroupOptions> optionsMonitor)
{
objectGroupOptionsMonitor = optionsMonitor;
InitializeComponent();
}
private void OnCheckBoxGroupChange(object sender, RoutedEventArgs e)
{
if (sender is not CheckBox checkbox) return;
if (checkbox.IsChecked == null) return;
var objectGroupProperty = ObjectGroupOptionsType?.GetProperties()
.FirstOrDefault(p => p.Name.Contains(checkbox.Name.Replace("CheckBox", "")));
ArgumentNullException.ThrowIfNull(objectGroupProperty);
objectGroupProperty.SetValue(objectGroupOptionsMonitor.CurrentValue, checkbox.IsChecked.Value);
}
}
Было найдено решение в виде биндинга один ко многим, как-то так:
public ObjectsGroupSettings()
{
InitializeComponent();
var binding = BindingOperations.GetBinding(FirstCheckBox, CheckBox.IsCheckedProperty);
FirstChild.SetBinding(CheckBox.IsCheckedProperty, binding);
SecondChild.SetBinding(CheckBox.IsCheckedProperty, binding);
ThirdChild.SetBinding(CheckBox.IsCheckedProperty, binding);
}
Вопрос, как сделать подобный биндинг один ко многим для ObjectGroupOptions и его слушателей ViewModels.
Заранее спасибо.