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" />

【讨论】:

TextBoxText 属性上的绑定默认为TwoWay

以上是关于CustomControl DependencyProperty 绑定无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章

拖放仅在其背景中工作的 CustomControl

CustomControl DependencyProperty 绑定无法正常工作

WPF Command

WP8日历(含农历)APP

WPF PasswordBox不支持绑定解决方法

WPF中的Pack URI