CustomControl DependencyProperty-Bindung funktioniert nicht richtig

Schrieb ich ein customcontrol. Es ist ein Textfeld mit einem button öffnet sich ein OpenFileDialog.

Die Text-Eigenschaft der TextBox gebunden ist meine Abhängigkeit Eigenschaft "FileName". Und wenn der Benutzer wählt eine Datei über OpenFileDialog, habe ich das Ergebnis auf diese Eigenschaft.

Die TextBox bekommt den richtigen Wert durch Bindung.

Aber nun mein problem. Für meine Ansicht, ich bin mit einem ViewModel. Also ich habe eine Bindung zu meinem DependencyProperty "Dateiname", um die Eigenschaft im ViewModel.
Nach dem ändern der "Dateiname" - Eigenschaft (änderungen direkt auf das Textfeld oder die Auswahl einer Datei über den dialog), der viewmodel-Eigenschaft wird nicht aktualisiert.

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>

Verwendung in einer Ansicht:

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

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

Ist es eine falsche Verwendung der dependency property?
Hinweis: Das problem tritt nur im DataGrid.

Vielleicht ist es wichtig zu sagen, dass dieses Problem nur Auftritt, wenn ich die Kontrolle in einer DataGridTemplateColumn von wpf-toolkit.
Das problem war, dass die ObservableCollection nicht erkennen, änderungen an der Liste der Elemente, nur, wenn Sie neue Elemente Hinzugefügt oder entfernt werden. Die Lösung ist das Konzept des VeryObservableCollection, die gefunden werden können hier auf stackoverflow.

InformationsquelleAutor Felix C | 2011-12-19

Schreibe einen Kommentar