2012-03-30 3 views
2

Dieses kann ein wenig lang erhalten, aber geht hier. Ich habe eine kleine Beispielanwendung im Wizard-Stil erstellt, die das MVVM-Muster verwendet (im Grunde eine abgespeckte Version des Codes in meiner "echten" App). In dieser App wird das Hauptfenster durch eine Liste der Ansichtsmodelle < ..> verschoben, wobei jedes Ansichtsmodell die zugehörige Ansicht anzeigt. Ich habe zwei Ansichtsmodellklassen, die im Wesentlichen identisch sind und dieselbe Ansicht anzeigen.MVVM IValueConverter Convert-Methode, die leeres Zeichenfolgenargument erhält, wenn man eine Hin- und Herbewegung erwartet

In der Ansicht ist ein Kombinationsfeld, bestückt mit einem Array von Float. Das SelectedItem ist an eine Float-Eigenschaft im Ansichtsmodell gebunden. Ich habe eine Vorlage für das Kombinationsfeld erstellt, um jedes Element als TextBlock anzuzeigen, wobei der Text den Gleitkommawert enthält und einen Wertkonverter durchläuft.

Das Problem, wenn ich zwischen Ansichtsmodellen hin- und herwechsle, funktioniert alles gut, solange jedes Ansichtsmodell, zu dem ich wechsle, von derselben Klasse ist. Sobald ich die aktuelle Seite in eine Instanz eines anderen Ansichtsmodells ändere, wird Convert des Wertkonverters mit einem Parameter 'value' aufgerufen, der eine Zeichenfolge mit der Länge null ist. Bis dahin wurde Convert nur mit einem Float aufgerufen, wie ich es erwarten würde.

Meine Frage: Warum wird der Konverter mit der leeren Zeichenkette ONLY aufgerufen, wenn man Klassenklassen wechselt?

Ich füge das Hauptfenster XAML an und betrachte das Modell sowie die Ansichts-/Ansichtsmodelle, die für jede "Seite" angezeigt werden. Sie werden feststellen, dass das Hauptfenstermodell eine Liste mit 2 Instanzen von PageViewModel und 2 Instanzen von OtherViewModel enthält. Ich kann zwischen den ersten beiden Strichen hin und her schalten, und der Wertwandler wird nur mit einem Float-Wert aufgerufen. Sobald ich zur ersten OtherViewModel-Instanz gewechselt habe, erhält der Konverter einen "extra" -Aufruf mit einer leeren Zeichenfolge als Wert.

-Code-Schnipsel:

  1. Mainwindow

    <Grid.Resources> 
        <DataTemplate DataType="{x:Type local:PageViewModel}"> 
         <local:PageView /> 
        </DataTemplate> 
        <DataTemplate DataType="{x:Type local:OtherViewModel}"> 
         <local:PageView /> 
        </DataTemplate> 
    </Grid.Resources> 
    
    <!-- Page --> 
    <ContentControl Margin="5,5,5,35" 
           Height="100" 
           IsTabStop="False" 
           Content="{Binding CurrentPage}" /> 
    
    <!-- Commands --> 
    <Button Margin="5,115,0,0" 
         Width="75" 
         Content="&lt; Back" 
         VerticalAlignment="Top" 
         HorizontalAlignment="Left" 
         Command="{Binding BackCommand}" /> 
    
    <Button Margin="85,115,0,0" 
         Width="75" 
         Content="Next &gt;" 
         VerticalAlignment="Top" 
         HorizontalAlignment="Left" 
         Command="{Binding NextCommand}" /> 
    
  2. MainWindowViewModel

    public MainWindowViewModel() 
        { 
         m_pages = new List<BaseViewModel>(); 
         m_pages.Add(new PageViewModel(1, 7f)); 
         m_pages.Add(new PageViewModel(2, 8.5f)); 
         m_pages.Add(new OtherViewModel(3, 10f)); 
         m_pages.Add(new OtherViewModel(4, 11.5f)); 
         m_currentPage = m_pages.First(); 
    
         m_nextCommand = new BaseCommand(param => this.OnNext(), param => this.EnableNext()); 
         m_backCommand = new BaseCommand(param => this.OnBack(), param => this.EnableBack()); 
        } 
    
        // Title 
    
        public string Title 
        { 
         get 
         { 
          return (CurrentPage != null) ? CurrentPage.Name : Name; 
         } 
        } 
    
        // Pages 
    
        BaseViewModel m_currentPage = null; 
        List<BaseViewModel> m_pages = null; 
    
        public BaseViewModel CurrentPage 
        { 
         get 
         { 
          return m_currentPage; 
         } 
         set 
         { 
          if (value == m_currentPage) 
          return; 
          m_currentPage = value; 
          OnPropertyChanged("Title"); 
          OnPropertyChanged("CurrentPage"); 
         } 
        } 
    
        // Back 
    
        ICommand m_backCommand = null; 
        public ICommand BackCommand 
        { 
         get 
         { 
          return m_backCommand; 
         } 
        } 
        public void OnBack() 
        { 
         CurrentPage = m_pages[m_pages.IndexOf(CurrentPage) - 1]; 
        } 
        public bool EnableBack() 
        { 
         return CurrentPage != m_pages.First(); 
        } 
    
        // Next 
    
        ICommand m_nextCommand = null; 
        public ICommand NextCommand 
        { 
         get 
         { 
          return m_nextCommand; 
         } 
        } 
        public void OnNext() 
        { 
         CurrentPage = m_pages[m_pages.IndexOf(CurrentPage) + 1]; 
        } 
        public bool EnableNext() 
        { 
         return CurrentPage != m_pages.Last(); 
        } 
    } 
    

Beachten Sie die 2 Instanz eines View-Modell durch zwei Instanzen der anderen folgte.

  1. Page-View-

    <Grid.Resources> 
        <x:Array x:Key="DepthList" 
          Type="sys:Single"> 
         <sys:Single>7</sys:Single> 
         <sys:Single>8.5</sys:Single> 
         <sys:Single>10</sys:Single> 
         <sys:Single>11.5</sys:Single> 
        </x:Array> 
        <local:MyConverter x:Key="MyConverter" /> 
    </Grid.Resources> 
    
    <TextBlock Text="Values:" 
          Margin="5,5,0,0"> 
    </TextBlock> 
    
    <ComboBox Width="100" 
          Height="23" 
          VerticalAlignment="Top" 
          HorizontalAlignment="Left" 
          Margin="5,25,0,0" 
          DataContext="{Binding}" 
          SelectedItem="{Binding Depth}" 
          ItemsSource="{Binding Source={StaticResource DepthList}}"> 
        <ComboBox.ItemTemplate> 
         <DataTemplate> 
          <TextBlock Text="{Binding Converter={StaticResource MyConverter}}" /> 
         </DataTemplate> 
        </ComboBox.ItemTemplate> 
    </ComboBox> 
    
  2. PageViewModel/OtherViewModel/MyConverter

    public class PageViewModel : BaseViewModel 
        { 
         public PageViewModel(int index, float depth) 
         { 
         Depth = depth; 
         Name = "Page #" + index.ToString(); 
         } 
    
         public float Depth 
         { 
         get; 
         set; 
         } 
        } 
    
        public class OtherViewModel : BaseViewModel 
        { 
         public OtherViewModel(int index, float depth) 
         { 
         Depth = depth; 
         Name = "Other #" + index.ToString(); 
         } 
    
         public float Depth 
         { 
         get; 
         set; 
         } 
        } 
    
        [ValueConversion(typeof(DateTime), typeof(String))] 
        public class MyConverter : IValueConverter 
        { 
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
         { 
         Debug.WriteLine("IValueConverter.Convert : received a " + value.GetType().Name); 
    
         string text = ""; 
         if (value is float) 
         { 
          text = value.ToString(); 
         } 
         else 
         { 
          throw new ArgumentException("MyConverter : input value is NOT a float."); 
         } 
    
         return text; 
         } 
    
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
         { 
         return float.Parse(value as string); 
         } 
        } 
    

Hinweis: Ich habe die Ausnahme in der Methode Konvertieren entfernen können, und alles scheint gut zu funktionieren. Aber ich würde gerne wissen, warum das passiert. Warum erhält der Konverter anstelle des erwarteten Floats eine leere Zeichenfolge, und nur wenn wir View-Modelle wechseln?

Alle Einsichten würden sehr geschätzt werden. Danke im Voraus... Joe

+0

Haben Sie versucht, DataType auf dem DataTemplate zu floaten? – Silvermind

+0

Silvermind, ich habe gerade versucht, DataType = "{x: Geben Sie sys: Single}" zu der DataTemplate-Zeile in der PageView, und ich bekomme immer noch das gleiche Problem. Ich bekomme immer noch einen Aufruf an den Konverter mit einer leeren Zeichenfolge als Wert. –

Antwort

3

Ich habe das gleiche Problem hatte (mit einem Enum anstelle eines float).

Wenn die Ansicht geschlossen ist, wird die ComboBox-Auswahl geleert. Sie können dies überprüfen, indem Sie das SelectionChanged-Ereignis behandeln und die SelectionChangedEventArgs RemovedItems-Auflistung überprüfen. Dies endet damit, dass String.Empty an den ValueConverter übergeben wird.

In meinem Fall habe ich den ValueConverter.Convert geändert, um string.Empty als gültigen Wert zuzulassen und string.Empty zurückzugeben. Dies ist der Code, den ich verwendet habe:

// When view is unloaded, ComboBox Selection is emptied and Convert is passed string.Empty 
// Hence we need to handle this conversion 
if (value is string && string.IsNullOrEmpty((string)value)) 
{ 
    return string.Empty; 
} 
1

Versuchen

public BaseViewModel 
{ 
    public virtual float Depth{get;set;} 
    ... 
} 

Dann

public class PageViewModel : BaseViewModel 
{ 
    ... 
    public override float Depth { get; set; } 
} 

und

public class OtherViewModel : BaseViewModel 
{ 
    ... 
    public override float Depth { get; set; } 
} 

Dann brauchen Sie nur ein Datatemplate

<Grid.Resources> 
    <DataTemplate DataType="{x:Type local:BaseViewModel}"> 
     <local:PageView /> 
    </DataTemplate> 
</Grid.Resources> 

Ich vermute, dass der seltsame Wert, der an den Konverter weitergegeben wird, auf DataTemplates zurückzuführen ist, die geschaltet werden.

Nicht getestet

+0

Phil, ich habe es versucht, und es scheint zu funktionieren. Ich musste nicht einmal die Ansichtsmodelle ändern, nur das DataTemplate, und das Problem verschwand. –

+0

Das heißt, das wäre immer noch ein Problem in Situationen, in denen das DataTemplate anders sein muss. In dieser Beispielanwendung habe ich die Ansicht zwischen zwei Ansichtsmodellen erneut verwendet. Aber in meiner komplexeren App wäre das keine Option. Also, immer noch nicht sicher, wie eine ausgewachsene Lösung. Werde mehr Suchen versuchen ... –