【问题标题】:CustomControl DependencyProperty Binding not working correctCustomControl DependencyProperty 绑定无法正常工作
【发布时间】: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 的概念,可以在 stackoverflow 上找到。

标签: wpf .net-3.5 custom-controls dependency-properties


【解决方案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
猜你喜欢
  • 1970-01-01
  • 2016-01-10
  • 2015-11-18
  • 1970-01-01
  • 1970-01-01
  • 2014-11-19
  • 2021-06-25
  • 2017-02-09
相关资源
最近更新 更多