I've this simplified case:
- MainWindow shows a TextBox and a UserControl.
- The UserControl has a dependency property to receive the TextBox text (bound in window XAML)
- The received text is bound to a view-model property in UserControl code-behind.
- The view-model converts the input text into camel case.
- Camel case text is displayed using a TextBlock in UserControl.
I can see the user control receives the correct value of the TextBox, however this value doesn't reach the view-model, I can't understand why.
MainWindow
Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp5"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<Label Content="Original:"/>
<TextBox x:Name="tb"/>
<local:CamelStringBox OriginalText="{Binding ElementName=tb, Path=Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Window>
Code-behind:
using System.Windows;
namespace WpfApp5 {
public partial class MainWindow : Window {
public MainWindow () {
InitializeComponent ();
}
}
}
UserControl CamelStringBox
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
x:Class="WpfApp5.CamelStringBox"
xmlns:local="clr-namespace:WpfApp5">
<UserControl.DataContext>
<local:CamelStringBoxViewModel x:Name="vm"/>
</UserControl.DataContext>
<StackPanel>
<Label Content="Camelized String:"/>
<TextBlock Text="{Binding ElementName=vm, Path=CamelizedText}"/>
</StackPanel>
</UserControl>
Code-behind:
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace WpfApp5 {
public partial class CamelStringBox : UserControl {
public string OriginalText {
get { return (string) GetValue (OriginalTextProperty); }
set { SetValue (OriginalTextProperty, value); }
}
public static readonly DependencyProperty OriginalTextProperty =
DependencyProperty.Register (
"OriginalText", typeof (string),
typeof (CamelStringBox),
new PropertyMetadata (
(d,e) => Debug.WriteLine ($"Value received by UserControl: {e.NewValue}")));
public CamelStringBox () {
InitializeComponent ();
// Bind user control OriginalText to view-model Text (doesn't work)
if (DataContext == null) Debug.WriteLine ($"{GetType ()}: DataContext is null");
else SetBinding (OriginalTextProperty, new Binding {
Path = new PropertyPath (nameof (CamelStringBoxViewModel.Text)),
Mode = BindingMode.TwoWay
});
}
}
public class CamelStringBoxViewModel : ChangeNotifier {
private string text;
private string camelizedText;
public string Text {
get => text;
set {
text = value;
RaisePropertyChanged ();
}
}
public string CamelizedText {
get => camelizedText;
private set {
camelizedText = value;
RaisePropertyChanged ();
}
}
// Never triggered
private void OnPropertyChanged (object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof (Text)) CamelizedText = Camelize (Text);
}
private string Camelize (string text) {
if (text == null || text.Length == 0) return null;
string[] s = text.ToLower ().Split (' ');
for (int i = 0; i < s.Length; i++) s[i] = char.ToUpper (s[i][0]) + s[i].Substring (1);
return string.Join (" ", s);
}
}
public class ChangeNotifier : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged ([CallerMemberName] string propertyName = null) {
PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (propertyName));
}
}
}