2014-01-06 2 views
0

У меня есть код ниже, который является упрощенным примером того, что я пытаюсь сделать. Я использую конвертер, чтобы попытаться заполнить DataGrid данными из модели, которая у меня есть. DataGrid заполняется правильно, но любые изменения в сетке не сохраняются обратно к объектам. Я указал режим как TwoWay. Когда я ставлю точку останова на конвертер ConvertBack, он никогда не вызывается.WPF IValueConverter.ConvertBack не называется

Я довольно новичок в WPF и MVVM, поэтому не вижу, что я делаю неправильно. Я не могу сделать, чтобы изменить модель, поэтому я хотел бы посмотреть, может ли это работать, если не существует явно превосходного метода.

XAML:

<Window x:Class="SampleBindingProblem.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:local="clr-namespace:SampleBindingProblem" 
     Title="MainWindow" Height="400" Width="500"> 
    <Window.Resources> 
     <ResourceDictionary> 
      <local:ScenarioDataTableConverter x:Key="ScenarioDataTableConverter" /> 
     </ResourceDictionary> 
    </Window.Resources> 
    <Grid> 
     <ListBox ItemsSource="{Binding Scenarios}"> 
      <ItemsControl.ItemTemplate> 
       <DataTemplate> 
        <DataGrid Margin="5" ItemsSource="{Binding Path=Options, Mode=TwoWay, Converter={StaticResource ScenarioDataTableConverter}}" /> 
       </DataTemplate> 
      </ItemsControl.ItemTemplate> 
     </ListBox> 
    </Grid> 
</Window> 

App.xaml.cs:

using System; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.ComponentModel; 
using System.Data; 
using System.Globalization; 
using System.Windows; 
using System.Windows.Data; 

namespace SampleBindingProblem 
{ 
    public class ColumnInfo 
    { 
     public static readonly String[] ColumnLabels = new String[] { "Variable1", "Variable2", "Variable3", "Variable4", "Variable5" }; 
    } 

    public class ScenarioOption 
    { 
     public String Label { get; set; } 
     public String[] Variables { get; set; } 
    } 

    public class Scenario 
    { 
     public ScenarioOption[] Options { get; set; } 
    } 

    internal class ScenarioDataTableConverter : IValueConverter 
    { 
     public Object Convert (Object value, Type targetType, Object parameter, CultureInfo culture) 
     { 
      if (value == null) 
       return (null); 

      ScenarioOption[] options = (ScenarioOption[]) value; 

      DataTable table = new DataTable(); 

      table.Columns.Add("Label", typeof(String)); 
      for (Int32 c = 0; c < ColumnInfo.ColumnLabels.Length; ++c) 
       table.Columns.Add(ColumnInfo.ColumnLabels[c], typeof(String)); 
      foreach (ScenarioOption option in options) 
      { 
       DataRow row = table.NewRow(); 
       List<String> lst = new List<String>(); 
       lst.Add(option.Label); 
       lst.AddRange(option.Variables); 
       row.ItemArray = lst.ToArray(); 
       table.Rows.Add(row); 
      } 

      return (table.DefaultView); 
     } 

     public Object ConvertBack (Object value, Type targetType, Object parameter, CultureInfo culture) 
     { 
      return (null); 
     } 
    } 

    internal class ViewModel : INotifyPropertyChanged 
    { 
     public void RaisePropertyChanged (String property) 
     { 
      if (this.PropertyChanged != null) 
       this.PropertyChanged(this, new PropertyChangedEventArgs(property)); 
     } 

     public event PropertyChangedEventHandler PropertyChanged = null; 

     public ObservableCollection<Scenario> Scenarios { get; set; } 

     public ViewModel() 
     { 
      Scenario s1 = new Scenario(); 
      s1.Options = new ScenarioOption[] { 
       new ScenarioOption() { Label = "Opt1", Variables=new String[] { "1", "2", "3", "4", "5" } }, 
       new ScenarioOption() { Label = "Opt2", Variables=new String[] { "2", "3", "4", "5", "6" } }, 
       new ScenarioOption() { Label = "Opt3", Variables=new String[] { "3", "4", "5", "6", "7" } }, 
      }; 
      Scenario s2 = new Scenario(); 
      s2.Options = new ScenarioOption[] { 
       new ScenarioOption() { Label = "Opt1", Variables=new String[] { "1", "2", "3", "4", "5" } }, 
       new ScenarioOption() { Label = "Opt2", Variables=new String[] { "2", "3", "4", "5", "6" } }, 
       new ScenarioOption() { Label = "Opt3", Variables=new String[] { "3", "4", "5", "6", "7" } }, 
      }; 

      this.Scenarios = new ObservableCollection<Scenario>(); 
      this.Scenarios.Add(s1); 
      this.Scenarios.Add(s2); 
     } 
    } 

    /// <summary> 
    /// Interaction logic for App.xaml 
    /// </summary> 
    public partial class App : Application 
    { 
     private void Application_Startup (Object sender, StartupEventArgs e) 
     { 
      MainWindow window = new MainWindow(); 
      window.DataContext = new ViewModel(); 
      window.ShowDialog(); 
     } 
    } 
} 
+0

Метод ConvertBack() в настоящее время возвращает null, потому что я его не реализовал, так как он до сих пор не был вызван. –

ответ

0

Это звучит как ошибка классического новичка ... Я думаю, что вам нужно реализовать INotifyPropertyChanged Interface в модели классов. Идея состоит в том, что вы сообщаете интерфейс INotifyPropertyChanged, когда изменяется какое-либо значение свойства. От связанной страницы на сайте MSDN:

public string CustomerName 
{ 
    get 
    { 
     return this.customerNameValue; 
    } 
    set 
    { 
     if (value != this.customerNameValue) 
     { 
      this.customerNameValue = value; 
      NotifyPropertyChanged(); 
     } 
    } 
} 

Пользовательский интерфейс может быть обновлен от класса модели и класс модели будет иметь возможность обновляться с изменений в пользовательском интерфейсе. Полный пример приведен на странице, посвященной MSDN.


Кроме того, вам не нужно объявлять ResourceDictionary в разделе Window.Resources ... это являетсяResourceDictionary:

<Window.Resources> 
    <local:ScenarioDataTableConverter x:Key="ScenarioDataTableConverter" /> 
</Window.Resources> 
+0

'INotifyPropertyChanged' предназначен для обновления пользовательского интерфейса. Его проблема в другом - обновление объектов из пользовательского интерфейса. –

+0

ResourceDictionary - это реликвия упрощения; в реальном коде есть другие вещи. Модель также представляет собой POCOs из другой сборки. Я надеюсь, что не придется добавлять это к другой сборке, но можно было бы наследовать класс и реализовывать интерфейс. –

2

преобразователи не работают таким образом, когда речь идет о коллекции. ConvertBack будет вызываться только тогда, когда будет заменена коллекция . Он не будет вызываться, когда элемент в коллекции будет изменен. В вашем случае коллекция (DataView) не заменяется новым экземпляром DataView, а скорее изменена, и поэтому ConvertBack не вызывается.

Если вы спросите меня, я не понимаю, почему вам нужно использовать конвертер в любом случае. Либо свяжите непосредственно с свойством Scenarios и работайте над этой коллекцией, которая открывается моделью просмотра, или, альтернативно, вызывается код преобразования в вашей модели просмотра и выставляет полученный результат DataView в другом свойстве. Тогда вам просто нужно привязать это свойство без указания конвертера.

+0

ListBox уже привязан к сценариям, поскольку представление позволяет разрешить несколько сценариев. Это сборка опций в сценарии, который я пытаюсь получить в сетке. Итак, что вы говорите, это реализовать свойство в Scenario, которое получает/задает DataView? –

+0

Это, вероятно, самый простой способ. Просто убедитесь, что вы удалите конвертер. –

+0

Я играл с этим, и до сих пор поведение кажется абсолютно таким же. Редактор DataView никогда не вызывается. –

Смежные вопросы