CustomControl DependencyProperty Binding не работает правильно - PullRequest
4 голосов
/ 19 декабря 2011

Я написал customcontrol. Это текстовое поле с кнопкой, которая открывает OpenFileDialog.

Свойство Text TextBox связано с моим свойством зависимости "FileName". И если пользователь выбирает файл через OpenFileDialog, я присваиваю результат этому свойству.

TextBox получает правильное значение с помощью привязки.

Но теперь моя проблема. На мой взгляд, я использую ViewModel. Поэтому у меня есть привязка к моему DependencyProperty «FileName» к свойству в моей ViewModel. После изменения свойства «FileName» (изменения непосредственно в текстовое поле или выбора файла через диалоговое окно) свойство 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.

Ответы [ 2 ]

15 голосов
/ 19 декабря 2011

Вам необходимо установить привязку 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);
1 голос
/ 19 декабря 2011

Изменить следующим образом:

<TextBox Name="txtFileName"
                     HorizontalAlignment="Stretch"
                     VerticalAlignment="Center"
                     Grid.Column="0"
                     Text="{Binding FileName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...