开发者

WPF Binding Issue - UI Updates, Object Does Not

开发者 https://www.devze.com 2023-01-31 06:51 出处:网络
I\'m having yet another WPF binding issue.Just when I think I\'ve got this stuff figured out, I run into more problems...:S

I'm having yet another WPF binding issue. Just when I think I've got this stuff figured out, I run into more problems... :S

Anyway... I've created a custom user control for selecting files. It's a simple textbox followed by a button contained within a grid. The property of the control with which I am working is called FilePath and the TextBox on this control is bound to that property. When the button is clicked, a SaveFileDialog is opened and the user selects a file. The UI correctly updates after the user selects the file.

The problem I seem to be having is that when I bind an object to the control (in this instance I have an object with a DocumentFilePath property) the object doesn't update when a new file is selected.

Here's the relevant code within my user control:

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

And the user control is added into my Window programatically by using reflection on my object:

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
}

It may be worth noting that if I load the window with an existing object, my user control displays properly but still won't register any changes within the object to which it is bound.

Please let me know if you guys need any more info.

Thanks in advance,

Sonny

EDIT: I've found a way around the problem, but this probably isn't a good solution. By watching the debugger carefully I found that when I set the FilePath property within my control, the object was being unbound. If anyone can shed some light on that, I would be most appreciative. In the mean time, I've changed the code that opens my SaveFileDialog to look like this:

private void Button_Click(object sender, RoutedEventArgs e)
{
    Microsoft.Win开发者_Python百科32.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;
    }
}


You're not specifying anywhere in your code that the FilePath property should be TwoWay so updates of the DP value won't get pushed to the bound source object's property. You can use either:

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

or you can set up your Dependency Property to use a default of TwoWay:

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

You should also follow Robert's suggestion of removing the manual PropertyChange event, and also don't EVER add any code other than GetValue and SetValue in your DP wrapper property. XAML calls GetValue and SetValue directly so will skip over anything else you add there - which can lead to very nasty bugs.


Why, yes! I most certainly can shed some light on that!

Also, if you're using .Net 4.0, today's your lucky day!

Consider the following fine method on your DependencyObject:

SetCurrentValue();

Yes! With this SINGULAR method, all your woes will drift away as a bad dream at the rooster's crow! (Well, ok, not really, but that is the method you're looking for.)

Short story very short: When you programmatically SetValue() on a control in your view layer, you blow away your bindings. SetCurrentValue() was added to the framework because you frequently want to drive a change in your bound object by setting that value directly. An alternate design would be to set the value in your bound object programmatically and let the updated value get pulled back into the view, but that's frequently clumsy.

(I strongly suspect that the absence of this method up to this point is largely responsible for the utter failure of the vast majority of NumericUpDown controls in WPF.)


First, you don't need to raise the PropertyChanged event when a dependency property changes; with dependency properties, change notification comes for free.

What's probably happening here: The default behavior for UpdateSourceTrigger is LostFocus, i.e. the source gets updated when the user presses TAB to move to the next field, or clicks on another control, or whatever. The text box isn't losing focus after your SaveFileDialog sets Text (since it probably doesn't even have the focus in the first place), so the source update never gets triggered.

To make it update the source whenever the Text property changes, set the UpdateSourceTrigger to PropertyChanged.

If that doesn't work, watch the Output window for binding errors.

Edit:

Here's a little prototype application I built. It works just fine: typing in the text box sets the property, clicking on the "Save" button sets the property, and the binding in the main window gets updated properly no matter what.

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

精彩评论

暂无评论...
验证码 换一张
取 消