CustomControl DependencyProperty 绑定无法正常工作 [英] CustomControl DependencyProperty Binding not working correct

查看:33
本文介绍了CustomControl DependencyProperty 绑定无法正常工作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我写了一个自定义控件.它是一个带有按钮的文本框,用于打开 OpenFileDialog.

I wrote a customcontrol. It is a textbox with a button which opens a OpenFileDialog.

TextBox 的 Text 属性绑定到我的依赖属性FileName".如果用户通过 OpenFileDialog 选择文件,我会将结果设置为此属性.

The Text property of the TextBox is bound to my dependency property "FileName". And if the user selects a file via the OpenFileDialog, i set the result to this property.

TextBox 通过绑定获得正确的值.

The TextBox gets the right value through binding.

但现在我的问题.对于我的观点,我使用的是 ViewModel.因此,我将我的 DependencyPropertyFileName"绑定到我的 ViewModel 中的属性.更改FileName"属性(直接更改为文本框或通过对话框选择文件)后,viewmodel 属性不会更新.

But now my problem. For my view I'm using a ViewModel. So I have a Binding to my DependencyProperty "FileName" to the property in my ViewModel. After changing the "FileName" property (changes direct to the textbox or selecting a file via the dialog), the viewmodel property doesn't update.

CustomControl.xaml.cs

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
    }
}

自定义控件.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>

和视图模型:

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 中.

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

推荐答案

您需要将绑定 Mode 设置为 TwoWay,因为默认绑定以一种方式工作,即加载从视图模型更改,但不会更新回来.

You need to set binding Mode to TwoWay, because by default binding works one way, i.e. loading changes from the view model, but not updating it back.

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

另一种选择是使用 BindsTwoWayByDefault 标志声明您的自定义依赖属性,如下所示:

Another option is to declare your custom dependency property with BindsTwoWayByDefault flag, like this:

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

此外,当您从控件内部更改自定义依赖项属性时,请使用 SetCurrentValue 方法,而不是使用属性设置器直接分配值.因为如果您直接分配它,您将打破绑定.

Also when you change your custom dependency property from inside your control use SetCurrentValue method instead of directly assigning the value using property setter. Because if you assign it directly you will break the binding.

所以,而不是:

FileName = dlg.FileName;

这样做:

SetCurrentValue(FileNameProperty, dlg.FileName);

这篇关于CustomControl DependencyProperty 绑定无法正常工作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆