CustomControl DependencyProperty Binding Handling Incorrect

I wrote customcontrol. This is a text box with a button that opens OpenFileDialog.

The Text property for the TextBox is tied to the FileName dependency property. And if the user selects the file through OpenFileDialog, I set the result to this property.

TextBox gets the correct value through binding.

But now my problem. In my opinion, I am using ViewModel. That way, I have a binding to my DependencyProperty "FileName" for the property in my ViewModel. After changing the "FileName" property (changing directly to a text field or selecting a file in a dialog box), the viewmodel property is not updated.

CustomControl.xaml.cs

using System.ComponentModel; using System.Windows; using System.Windows.Controls; using Microsoft.Win32; namespace WpfApplication1.CustomControl { /// <summary> /// Interaction logic for FileSelectorTextBox.xaml /// </summary> public partial class FileSelectorTextBox : UserControl, INotifyPropertyChanged { public FileSelectorTextBox() { InitializeComponent(); DataContext = this; } #region FileName dependency property public static readonly DependencyProperty FileNameProperty = DependencyProperty.Register( "FileName", typeof(string), typeof(FileSelectorTextBox), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnFileNamePropertyChanged), new CoerceValueCallback(OnCoerceFileNameProperty))); public string FileName { get { return (string)GetValue(FileNameProperty); } set { /*SetValue(FileNameProperty, value);*/ CoerceFileName(value); } } private bool _shouldCoerceFileName; private string _coercedFileName; private object _lastBaseValueFromCoercionCallback; private object _lastOldValueFromPropertyChangedCallback; private object _lastNewValueFromPropertyChangedCallback; private object _fileNameLocalValue; private ValueSource _fileNameValueSource; private static void OnFileNamePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is FileSelectorTextBox) { (d as FileSelectorTextBox).OnFileNamePropertyChanged(e); } } private void OnFileNamePropertyChanged(DependencyPropertyChangedEventArgs e) { LastNewValueFromPropertyChangedCallback = e.NewValue; LastOldValueFromPropertyChangedCallback = e.OldValue; FileNameValueSource = DependencyPropertyHelper.GetValueSource(this, FileNameProperty); FileNameLocalValue = this.ReadLocalValue(FileNameProperty); } private static object OnCoerceFileNameProperty(DependencyObject d, object baseValue) { if (d is FileSelectorTextBox) { return (d as FileSelectorTextBox).OnCoerceFileNameProperty(baseValue); } else { return baseValue; } } private object OnCoerceFileNameProperty(object baseValue) { LastBaseValueFromCoercionCallback = baseValue; return _shouldCoerceFileName ? _coercedFileName : baseValue; } internal void CoerceFileName(string fileName) { _shouldCoerceFileName = true; _coercedFileName = fileName; CoerceValue(FileNameProperty); _shouldCoerceFileName = false; } #endregion FileName dependency property #region Public Properties public ValueSource FileNameValueSource { get { return _fileNameValueSource; } private set { _fileNameValueSource = value; OnPropertyChanged("FileNameValueSource"); } } public object FileNameLocalValue { get { return _fileNameLocalValue; } set { _fileNameLocalValue = value; OnPropertyChanged("FileNameLocalValue"); } } public object LastBaseValueFromCoercionCallback { get { return _lastBaseValueFromCoercionCallback; } set { _lastBaseValueFromCoercionCallback = value; OnPropertyChanged("LastBaseValueFromCoercionCallback"); } } public object LastNewValueFromPropertyChangedCallback { get { return _lastNewValueFromPropertyChangedCallback; } set { _lastNewValueFromPropertyChangedCallback = value; OnPropertyChanged("LastNewValueFromPropertyChangedCallback"); } } public object LastOldValueFromPropertyChangedCallback { get { return _lastOldValueFromPropertyChangedCallback; } set { _lastOldValueFromPropertyChangedCallback = value; OnPropertyChanged("LastOldValueFromPropertyChangedCallback"); } } #endregion FileName dependency property private void btnBrowse_Click(object sender, RoutedEventArgs e) { FileDialog dlg = null; dlg = new OpenFileDialog(); bool? result = dlg.ShowDialog(); if (result == true) { FileName = dlg.FileName; } txtFileName.Focus(); } #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion INotifyPropertyChanged } } 

CustomControl.xaml

 <UserControl x:Class="WpfApplication1.CustomControl.FileSelectorTextBox" 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" d:DesignHeight="23" d:DesignWidth="300"> <Border BorderBrush="#FF919191" BorderThickness="0"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" MinWidth="80" /> <ColumnDefinition Width="30" /> </Grid.ColumnDefinitions> <TextBox Name="txtFileName" HorizontalAlignment="Stretch" VerticalAlignment="Center" Grid.Column="0" Text="{Binding FileName}" /> <Button Name="btnBrowse" Click="btnBrowse_Click" HorizontalContentAlignment="Center" ToolTip="Datei auswählen" Margin="1,0,0,0" Width="29" Padding="1" Grid.Column="1"> <Image Source="../Resources/viewmag.png" Width="15" Height="15" /> </Button> </Grid> </Border> </UserControl> 

Usage in view:

 <Window x:Class="WpfApplication1.MainView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:WpfApplication1.ViewModels" xmlns:controls="clr-namespace:WpfApplication1.CustomControl" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <vm:MainViewModel /> </Window.DataContext> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="10" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <DataGrid ItemsSource="{Binding Files}" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTemplateColumn Header="File name" Width="*"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <controls:FileSelectorTextBox FileName="{Binding .}" Height="30" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid> <ListBox ItemsSource="{Binding Files}" Grid.Row="2"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding}" /> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Window> 

And ViewModel:

 using System.Collections.ObjectModel; using System.ComponentModel; namespace WpfApplication1.ViewModels { internal class MainViewModel : INotifyPropertyChanged { public MainViewModel() { Files = new ObservableCollection<string> { "test1.txt", "test2.txt", "test3.txt", "test4.txt" }; } #region Properties private ObservableCollection<string> _files; public ObservableCollection<string> Files { get { return _files; } set { _files = value; OnPropertyChanged("Files"); } } #endregion Properties #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion INotifyPropertyChanged Members } } 

Is there any misuse of the dependency property? Note. The problem only occurs in the DataGrid.

+4
source share
2 answers

You need to set the Mode binding to TwoWay , because by default the binding works in one way, that is, it loads the changes from the view model, but does not update it.

 <controls:FileSelectorTextBox FileName="{Binding FileName, Mode=TwoWay}" Height="30" /> 

Another option is to declare your custom dependency property with the BindsTwoWayByDefault flag, for example:

 public static readonly DependencyProperty FileNameProperty = DependencyProperty.Register("FileName", typeof(string), typeof(FileSelectorTextBox), new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); 

Also, when you change your own dependency property inside your control, use the SetCurrentValue method instead of directly assigning a value using the property setting tool. Because if you assign it directly, you break the binding.

So, instead of:

 FileName = dlg.FileName; 

Do the following:

 SetCurrentValue(FileNameProperty, dlg.FileName); 
+15
source

Change as follows:

 <TextBox Name="txtFileName" HorizontalAlignment="Stretch" VerticalAlignment="Center" Grid.Column="0" Text="{Binding FileName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> 
+1
source

Source: https://habr.com/ru/post/1387009/


All Articles