I'm quite new with WPF and I might be missing something simple, but I've tried with some simple examples for Custom User Control Binding and they work, but when I try to apply them to our situation, the binding only works when I modify the value in the Custom User Control, but if I change it from other place the Custom User Control does not reflect that change.
We are using Prism and we have a ViewModel and a View to show and edit the properties of a Device. These properties are read from an XML at startup and added to the Device as a Dictionary of Settings. Both Settings and Device implement INotifyPropertyChanged and when a Setting's value changes, the Device raises the property Changed event for that Setting as well.
So when the program is started the values are shown like this, where the red arrow is the new custom user control and blue arrow points the working code directly in the view initial state:
If I change the value in the custom user control, its updated in the other one as well. Binding to source ok:
But if I change it on the other one, its not updated in the custom user control. Binding from source not ok:
Also, if I change the value of the str_pressureUnit condition, the conversion is only performed in the old code. Conversion condition binding not ok:
But the Enable/Disable Control, works correctly for both. Enable ok:
Simple examples with another custom user control and properties from Device that are not Dynamic work ok if I use the `Mode="TwoWay". I was thinking that maybe is a problem with the Multibindings or with the Dynamic Settings configuration, but as the one with EnableProperties work (which is a normal property of the ViewModel) I suspect it might be something related to the Dynamic properties.
This is how our Device class looks like:
public class Device : DynamicObject, INotifyPropertyChanged
{
#region Properties
...
public Dictionary<string, ISetting> DynamicSettings { get; private set; } = new Dictionary<string, ISetting>();
...
public Device(SettingDefinitionsProvider settingDefinitionsProvider, ICommunicationChannel communicationChannel, DeviceType deviceType)
{
...
foreach(ISetting s in DynamicSettings.Values)
{
s.PropertyChanged += OnSettingValueUpdated;
}
}
...
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName]string propertyname = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
#endregion //INotifyPropertyChanged
#region EventHandlers
private void OnSettingValueUpdated(object sender, PropertyChangedEventArgs e)
{
if(sender is ISetting)
{
OnPropertyChanged((sender as ISetting).Name);
}
}
#endregion //EventHandlers
}
And the Setting class throws the PropertyChanged event when its value changes.
The viewModel has a SelectedDevice property and its set as DataContext of the view with the AutoWireViewModel functionality of Prism.
We are also using MultiBinding and some complex converters because some Settings shown value might change depending on other Setting Value, as well the control being enabled, etc. So one entry in the XAML for a Setting might get this big:
<TextBlock Grid.Row="4" Text="{Binding SelectedDevice.SafetyMargin.DisplayName}" Margin="5"/>
<TextBox Grid.Row="4" Grid.Column="1" Margin="5">
<TextBox.IsEnabled>
<MultiBinding Converter="{conv:EnableControlConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type UserControl}, Mode=FindAncestor}" Path="DataContext.SelectedDevice.SafetyMargin"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type UserControl}, Mode=FindAncestor}" Path="DataContext.EnableControls"/>
</MultiBinding>
</TextBox.IsEnabled>
<MultiBinding Converter="{conv:ConversionConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type UserControl}, Mode=FindAncestor}" Path="DataContext.SelectedDevice.SafetyMargin"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type UserControl}, Mode=FindAncestor}" Path="DataContext.SelectedDevice.PressureUnit"/>
</MultiBinding>
</TextBox>
<TextBlock Grid.Row="4" Grid.Column="2" Margin="5">
<TextBlock.Text>
<MultiBinding Converter="{conv:UnitLabelConverter}">
<Binding RelativeSource="{RelativeSource AncestorType={x:Type UserControl}, Mode=FindAncestor}" Path="DataContext.SelectedDevice.SafetyMargin"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type UserControl}, Mode=FindAncestor}" Path="DataContext.SelectedDevice.PressureUnit"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
To simplify the addition of new Settings (this is the reason of the reading from xml and so on), I created a new user Control. Its XAML is:
<UserControl ...
x:Name="parent">
<Grid DataContext="{Binding ElementName=parent}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding Path=Setting.DisplayName}" Margin="5"/>
<TextBox Grid.Column="1" Margin="5">
<TextBox.IsEnabled>
<MultiBinding Converter="{conv:EnableControlConverter}">
<Binding Path="Setting"/>
<Binding Path="Enabled"/>
</MultiBinding>
</TextBox.IsEnabled>
<MultiBinding Mode="TwoWay" Converter="{conv:ConversionConverter}">
<Binding Path="Setting"/>
<Binding Path="ConditionSetting"/>
</MultiBinding>
</TextBox>
<TextBlock Grid.Column="2" Margin="5">
<TextBlock.Text>
<MultiBinding Converter="{conv:UnitLabelConverter}">
<Binding Path="Setting"/>
<Binding Path="ConditionSetting"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
And the code behind:
public partial class CustomTextBox : UserControl
{
protected static Logger logger = LogManager.GetCurrentClassLogger();
public static readonly DependencyProperty SettingProperty =
DependencyProperty.Register("Setting", typeof(object),
typeof(CustomTextBox), new PropertyMetadata(""));
public static readonly DependencyProperty ConditionSettingProperty =
DependencyProperty.Register("ConditionSetting", typeof(object),
typeof(CustomTextBox), new PropertyMetadata(""));
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.Register("Enabled", typeof(object),
typeof(CustomTextBox), new PropertyMetadata(""));
public object Setting
{
get { return (object)GetValue(SettingProperty); }
set { SetValue(SettingProperty, value); }
}
public object ConditionSetting
{
get { return (object)GetValue(ConditionSettingProperty); }
set { SetValue(ConditionSettingProperty, value); }
}
public object Enabled
{
get { return (object)GetValue(EnabledProperty); }
set { SetValue(EnabledProperty, value); }
}
public CustomTextBox()
{
InitializeComponent();
}
}
And this is the new code in the view:
<controls:CustomTextBox Grid.Row="3"
Setting="{Binding SelectedDevice.SafetyMargin, Mode=TwoWay}"
ConditionSetting="{Binding SelectedDevice.PressureUnit, Mode=TwoWay}"
Enabled="{Binding EnableControls}"/>
Any advice will be highly appreciated, thanks in advance.