Проблема привязки WPF - обновления пользовательского интерфейса, объект не - PullRequest
4 голосов
/ 20 декабря 2010

У меня возникла еще одна проблема с привязкой WPF. Просто, когда я думаю, что все это выяснили, я сталкиваюсь с большим количеством проблем ...: S

В любом случае ... Я создал пользовательский элемент управления для выбора файлов. Это простое текстовое поле, за которым следует кнопка, содержащаяся в сетке. Свойство элемента управления, с которым я работаю, называется FilePath, и TextBox для этого элемента управления привязан к этому свойству. При нажатии кнопки открывается SaveFileDialog, и пользователь выбирает файл. Пользовательский интерфейс корректно обновляется после выбора файла пользователем.

Проблема, с которой я столкнулся, заключается в том, что когда я связываю объект с элементом управления (в данном случае у меня есть объект со свойством 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 голосов
/ 21 декабря 2010

Вы нигде в своем коде не указываете, что свойство 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));

Вы также должны следовать предложению Роберта об удалении ручного события PropertyChange вручную., а также НИКОГДА не добавляйте код, отличный от GetValue и SetValue, в свойстве оболочки DP.XAML вызывает GetValue и SetValue напрямую, поэтому пропускает все, что вы добавляете туда, что может привести к очень неприятным ошибкам.

2 голосов
/ 21 декабря 2010

Почему, да! Я, безусловно, могу пролить свет на это!

Кроме того, если вы используете .Net 4.0, сегодня ваш счастливый день!

Рассмотрим следующий тонкий метод для вашего объекта DependencyObject:

SetCurrentValue();

Да! С этим СИНГУЛЯРНЫМ методом все твои беды исчезнут как дурной сон на вороне петуха! (Ну, хорошо, не совсем, но это метод, который вы ищете.)

Короткая история очень короткая: когда вы программно SetValue() управляете своим слоем вида, вы уничтожаете свои привязки. SetCurrentValue () был добавлен в платформу, потому что вы часто хотите внести изменения в связанный объект, устанавливая это значение напрямую. Альтернативный дизайн - установить значение в вашем связанном объекте программным способом и позволить обновленному значению вернуться в представление, но это часто неуклюже.

(Я сильно подозреваю, что отсутствие этого метода до этого момента в значительной степени является причиной полного отказа подавляющего большинства элементов управления NumericUpDown в WPF.)

1 голос
/ 21 декабря 2010

Во-первых, вам не нужно вызывать событие 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...