CustomControl DependencyProperty 绑定无法正常工作
Posted
技术标签:
【中文标题】CustomControl DependencyProperty 绑定无法正常工作【英文标题】:CustomControl DependencyProperty Binding not working correct 【发布时间】:2021-11-08 19:02:41 【问题描述】:我写了一个自定义控件。它是一个带有打开 OpenFileDialog 按钮的文本框。
TextBox 的 Text 属性绑定到我的依赖属性“FileName”。如果用户通过 OpenFileDialog 选择一个文件,我将结果设置为此属性。
TextBox 通过绑定获取正确的值。
但现在我的问题。对于我的观点,我使用的是 ViewModel。所以我有一个绑定到我的 DependencyProperty“文件名”到我的 ViewModel 中的属性。 更改“文件名”属性(直接更改为文本框或通过对话框选择文件)后,视图模型属性不会更新。
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>
在视图中使用:
<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>
还有 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
依赖属性的使用有什么问题吗? 注意:该问题仅出现在 DataGrid 中。
【问题讨论】:
也许重要的是要说这个问题只有在我使用 wpf 工具包的 DataGridTemplateColumn 中的控件时才会出现。 问题在于 ObservableCollection 无法识别列表元素的更改,即使添加或删除了新元素。解决方案是 VeryObservableCollection 的概念,可以在 *** 上找到。 【参考方案1】:您需要将绑定Mode
设置为TwoWay
,因为默认情况下绑定以一种方式工作,即从视图模型加载更改,但不更新回来。
<controls:FileSelectorTextBox FileName="Binding FileName, Mode=TwoWay" Height="30" />
另一种选择是使用BindsTwoWayByDefault
标志声明您的自定义依赖项属性,如下所示:
public static readonly DependencyProperty FileNameProperty =
DependencyProperty.Register("FileName",
typeof(string),
typeof(FileSelectorTextBox),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
此外,当您从控件内部更改自定义依赖项属性时,请使用SetCurrentValue
方法,而不是使用属性设置器直接分配值。因为如果你直接分配它,你会破坏绑定。
所以,而不是:
FileName = dlg.FileName;
这样做:
SetCurrentValue(FileNameProperty, dlg.FileName);
【讨论】:
哦,对不起,我的原始帖子中的代码错误。我通过框架属性元数据设置了默认绑定模式 TwoWay @FelixCzylwik - 查看我更新的答案(关于如何从控件中设置依赖属性的值)。 嗯,这正是我的问题,但似乎 SetCurrentValue 在 .NET 3.5 中不存在 @FelixCzylwik - 似乎有一种使用 CoerceValue 方法的解决方法。查看此链接:arbel.net/2009/11/04/local-values-in-dependencyobjects 我更新了我的原始帖子.. 它也不适用于 CoerceValue :(【参考方案2】:修改如下:
<TextBox Name="txtFileName"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Grid.Column="0"
Text="Binding FileName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged" />
【讨论】:
在TextBox
的Text
属性上的绑定默认为TwoWay
。以上是关于CustomControl DependencyProperty 绑定无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章