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

4
我又遇到了一个WPF绑定问题。就在我认为我已经弄清楚这些东西的时候,我又遇到了更多的问题... :S
无论如何...我创建了一个用于选择文件的自定义用户控件。它是一个简单的文本框,后面跟着一个包含在网格中的按钮。我正在处理的控件属性名为FilePath,该控件上的文本框与该属性绑定。当单击按钮时,会打开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
}

值得注意的是,如果我加载一个已存在的对象窗口,我的用户控件将正确显示,但仍无法注册到其绑定的对象中进行任何更改。如果您需要更多信息,请告诉我。
谢谢,Sonny
编辑:我已经找到了解决问题的方法,但这可能不是一个好的解决方案。通过仔细观察调试器,我发现当我在控件内设置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个回答

3

你的代码中没有指定 FilePath 属性应该是双向绑定的,因此 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,因此将跳过您在那里添加的任何其他内容-这可能会导致非常严重的错误。


2

是的,我当然可以为您阐明一下!

如果您使用的是.Net 4.0版本,那么今天就是你的幸运日!

请考虑针对您的DependencyObject使用以下优秀的方法:

SetCurrentValue();

是的!使用这个唯一的方法,所有问题都将像被叫醒的公鸡发出的声音一样消失不见!(好吧,事实并非如此,但这确实是您要查找的方法。)

简而言之:当您在视图层上以编程方式SetValue()控制时,会破坏您的绑定。因为常常需要通过直接设置该值来驱动绑定对象的更改,所以添加了SetCurrentValue()到框架中。另一种设计方法是在代码中以编程方式设置绑定对象的值并将更新后的值拉回视图层,但这通常很笨拙。

(我强烈怀疑,直到现在没有这个方法,大部分WPF中的NumericUpDown控件都彻底失败了。)


他在他的代码中做了什么,而我没有做的呢?为什么我的“FilePickerSaveCommand”代码不能消除绑定? - Robert Rossney
现在我的绑定已经保持,但是绑定对象仍然没有更新为新值。 - Sonny Boy

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

谢谢,罗伯特。不过两个选项都没有成功。我已经找到了一个解决方法,也许它可以揭示出问题出在哪里?请看我在原帖中的编辑。 - Sonny Boy
关于你的编辑,Robert,ICommand服务器有什么作用?我的UserControl没有它。 - Sonny Boy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接