WPF绑定问题 - UI更新,对象不

时间:2010-12-20 19:03:22

标签: c# wpf data-binding binding

我还有另一个WPF绑定问题。就在我认为我已经找到了这些东西的时候,我遇到了更多的问题......:S

无论如何......我已经创建了一个用于选择文件的自定义用户控件。它是一个简单的文本框,后跟一个包含在网格中的按钮。我正在使用的控件的属性称为FilePath,此控件上的TextBox绑定到该属性。单击该按钮时,将打开SaveFileDialog,用户选择一个文件。用户选择文件后,UI会正确更新。

我似乎遇到的问题是,当我将一个对象绑定到控件时(在这个例子中我有一个带有DocumentFilePath属性的对象),当选择一个新文件时,该对象不会更新。

以下是我的用户控件中的相关代码:

public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register("FilePath", typeof(string), typeof(FileSave), new UIPropertyMetadata(string.Empty, OnFilePathChanged));

public string FilePath
{
    get
    {
        return this.GetValue(FilePathProperty) as string;
    }
    set
    {
        this.SetValue(FilePathProperty, value);
        this.OnPropertyChanged("FilePath");
    }
}

private void OnPropertyChanged(string propName)
{
    if (PropertyChanged != null)
    {
       PropertyChanged(this, new PropertyChangedEventArgs(propName));
    }
}

private static void OnFilePathChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    ((FileSave)sender).OnPropertyChanged("FilePath");
}

用我的对象上的反射以编程方式将用户控件添加到我的窗口中:

private void AddFileSave(PropertyInfo pi)
{
     FileSave fs = new FileSave();
     Binding b = new Binding(pi.Name);

     fs.SetBinding(FileSave.FilePathProperty, b);
     this.AddToGrid(fs); //adds the control into my window's grid in the correct row and column; nothing fancy here
}

值得注意的是,如果我使用现有对象加载窗口,我的用户控件将正确显示,但仍然不会在其绑定的对象中注册任何更改。

如果你们需要更多信息,请告诉我。

提前致谢,
桑尼

编辑:我找到了解决问题的方法,但这可能不是一个好方法。通过仔细观察调试器,我发现当我在控件中设置FilePath属性时,该对象未被绑定。如果有人能够对此有所了解,我将非常感激。与此同时,我已经将打开我的SaveFileDialog的代码更改为:

private void Button_Click(object sender, RoutedEventArgs e)
{
    Microsoft.Win32.OpenFileDialog ofd = new Microsoft.Win32.OpenFileDialog();

    ofd.Multiselect = false;
    ofd.Title = "Select document to import...";
    ofd.ValidateNames = true;

    ofd.ShowDialog();

    if (this.GetBindingExpression(FilePathProperty) == null)
    {
        this.FilePath = ofd.FileName;
    }
    else //set value on bound object (THIS IS THE NEW PORTION I JUST ADDED)
    {
        BindingExpression be = this.GetBindingExpression(FilePathProperty);
        string propName = be.ParentBinding.Path.Path;
        object entity = be.DataItem;
        System.Reflection.PropertyInfo pi = entity.GetType().GetProperty(propName);

        pi.SetValue(entity, ofd.FileName, null);
    }

    if (!string.IsNullOrWhiteSpace(this.FilePath))
    {
        _fileContents = new MemoryStream();
        using (StreamReader sr = new StreamReader(this.FilePath))
        {
            _fileContents = new MemoryStream(System.Text.ASCIIEncoding.ASCII.GetBytes(sr.ReadToEnd()));
        }
    }
    else
    {
        _fileContents = null;
    }
}

3 个答案:

答案 0 :(得分:3)

您没有在代码中的任何位置指定FilePath属性应该是TwoWay,因此DP值的更新不会被推送到绑定的源对象的属性。您可以使用:

Binding b = new Binding(pi.Name){ Mode = BindingMode.TwoWay };

或者您可以设置您的依赖属性以使用默认值TwoWay:

public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register(
"FilePath", typeof(string), typeof(FileSave),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnFilePathChanged));

您还应该遵循Robert关于删除手动PropertyChange事件的建议,也不要在DP包装器属性中添加除GetValue和SetValue之外的任何代码。 XAML直接调用GetValue和SetValue,因此会跳过你在那里添加的任何其他东西 - 这可能会导致非常讨厌的错误。

答案 1 :(得分:2)

为什么,是的!我当然可以对此有所了解!

另外,如果您使用的是.Net 4.0,今天是您的幸运日!

在DependencyObject上考虑以下精细方法:

SetCurrentValue();

是的!有了这种奇异的方法,你所有的困境都会在公鸡的乌鸦中作为一个糟糕的梦想而消失! (好吧,不,不是真的,但那是你正在寻找的方法。)

简短的故事非常简短:当您在视图层中的控件上以编程方式SetValue()时,您就会吹掉绑定。 SetCurrentValue()已添加到框架中,因为您经常希望通过直接设置该值来驱动绑定对象的更改。另一种设计是以编程方式设置绑定对象中的值,并将更新后的值拉回到视图中,但这通常很笨拙。

(我强烈怀疑到目前为止这种方法的缺失是WPF中绝大多数NumericUpDown控件彻底失败的主要原因。)

答案 2 :(得分:1)

首先,当依赖属性更改时,您不需要引发PropertyChanged事件;使用依赖属性,更改通知是免费的。

这里可能会发生什么:UpdateSourceTrigger的默认行为是LostFocus,即当用户按TAB移动到下一个字段或点击其他控件或其他任何内容时,源会更新。在SaveFileDialog设置Text之后,文本框不会失去焦点(因为它可能首先没有焦点),因此源更新永远不会被触发。

要在Text属性更改时更新源,请将UpdateSourceTrigger设置为PropertyChanged

如果不起作用,请观看“输出”窗口以查找绑定错误。

修改

这是我构建的一个小原型应用程序。它工作得很好:在文本框中输入设置属性,单击“保存”按钮设置属性,无论如何,主窗口中的绑定都会正确更新。

<Window x:Class="DependencyPropertyBindingDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:demo="clr-namespace:DependencyPropertyBindingDemo" 
        Title="MainWindow" Height="350" Width="525">
    <DockPanel>
        <demo:FilePicker x:Name="Picker"
                         DockPanel.Dock="Top"
                         Margin="5" /> 
        <TextBox DockPanel.Dock="Top" 
                Text="{Binding ElementName=Picker, Path=FilePath}" />
        <TextBlock />
    </DockPanel>
</Window>

<UserControl x:Class="DependencyPropertyBindingDemo.FilePicker"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <DockPanel>
        <TextBox DockPanel.Dock="Left" 
                 Width="200"
                 Text="{Binding FilePath, UpdateSourceTrigger=PropertyChanged}" />
        <Button Width="50"
                DockPanel.Dock="Left"
                Command="{Binding Path=SaveCommand}">Save</Button>
        <TextBlock />
    </DockPanel>
</UserControl>

public partial class FilePicker : UserControl
{
    public FilePicker()
    {
        SaveCommand = new FilePickerSaveCommand(this);
        DataContext = this;
        InitializeComponent();
    }

    public ICommand SaveCommand { get; set; }

    public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register("FilePath", typeof(string), typeof(FilePicker));

    public string FilePath
    {
        get
        {
            return GetValue(FilePathProperty) as string;
        }
        set
        {
            SetValue(FilePathProperty, value);
        }
    }
}

public class FilePickerSaveCommand : ICommand
{
    private FilePicker _FilePicker;

    public FilePickerSaveCommand(FilePicker picker)
    {
        _FilePicker = picker;
    }

    public void Execute(object parameter)
    {
        _FilePicker.FilePath = "Testing";
    }

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public event EventHandler CanExecuteChanged;
}