2011-12-19 89 views
4

我写了一个自定义控件。它是一个带有一个打开OpenFileDialog的按钮的文本框。CustomControl DependencyProperty绑定不能正常工作

TextBox的Text属性绑定到我的依赖项属性“FileName”。如果用户通过OpenFileDialog选择一个文件,我将结果设置为该属性。

TextBox通过绑定获取正确的值。

但现在我的问题。对于我的看法,我正在使用ViewModel。所以我有一个绑定到我的DependencyProperty“文件名”我的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> 

和视图模型:

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

+0

也许可以这样说,如果我用这个问题只出现很重要在WPF工具包的DataGridTemplateColumn中的控件。 – 2011-12-19 10:40:03

+0

问题在于ObservableCollection不能识别对列表元素的更改,只是添加或删除了新元素。解决方案是VeryObservableCollection的概念,可以在这里找到stackoverflow。 – 2012-01-06 13:00:16

回答

15

您需要设置绑定ModeTwoWay,因为默认情况下结合的作品的一种方式,即装载从视图模型的变化,但不更新回来。

<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); 
+0

噢,我很抱歉在我原来的帖子错误的代码..我设置默认绑定模式TwoWay由frameworkpropertymetadata – 2011-12-19 10:07:28

+0

@FelixCzylwik - 看我更新的答案(关于如何设置从控件内的依赖属性的值) 。 – 2011-12-19 10:16:16

+0

恩,这正是我的问题,但似乎.NET 3.5中不存在SetCurrentValue – 2011-12-19 10:34:14

1

变化如下:

<TextBox Name="txtFileName" 
        HorizontalAlignment="Stretch" 
        VerticalAlignment="Center" 
        Grid.Column="0" 
        Text="{Binding FileName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> 
+1

来更新控件内部的绑定。默认情况下,'TextBox'的'Text'属性的绑定默认为'TwoWay' 。 – 2011-12-19 10:22:28