MVVM IValueConverter Метод Convert, получающий пустой строковый аргумент при ожидании float - PullRequest
2 голосов
/ 30 марта 2012

Это может быть немного долго, но здесь идет. Я создал небольшое примерное приложение в стиле мастера, используя шаблон MVVM (в основном, дурацкая версия кода в моем «реальном» приложении). В этом приложении главное окно перемещается из списка <..> моделей представлений, причем каждая модель представления отображает свое связанное представление. У меня есть 2 класса модели представления, которые по существу идентичны, и они отображают одно и то же представление.

В представлении находится поле со списком, заполненное массивом с плавающей точкой. SelectedItem привязан к свойству float в модели представления. Я создал шаблон для поля со списком, чтобы отобразить каждый элемент в виде TextBlock с текстом, принимающим значение с плавающей запятой и проходящим через преобразователь значений.

Проблема, когда я переключаюсь между моделями представлений, все работает нормально, пока все модели представлений, на которые я переключаюсь, принадлежат к одному и тому же классу. Как только я изменяю текущую страницу на экземпляр другой модели представления, Convert конвертера значений вызывается с параметром 'value', который является строкой нулевой длины. Вплоть до того времени, как я и ожидал, Конверта вызывали только с плавающей точкой.

Мой вопрос: почему конвертер вызывается ТОЛЬКО с пустой строкой в ​​случае переключения классов моделей представления?

Я прикрепляю главное окно XAML и представление модели, а также просмотр / просмотр моделей, отображаемых для каждой «страницы». Вы заметите, что модель представления основного окна имеет список, содержащий 2 экземпляра PageViewModel и 2 экземпляра OtherViewModel. Я могу переключаться между первыми 2 точными, и преобразователь значений вызывается только с плавающим значением. Как только я переключаюсь на первый экземпляр OtherViewModel, преобразователь получает «дополнительный» вызов с пустой строкой в ​​качестве значения.

Фрагменты кода:

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

Обратите внимание на 2 экземпляра одной модели представления, за которыми следуют 2 экземпляра другой.

  1. страницуПоказать

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

Примечание: я могу удалить исключение в методе Convert, и все, кажется, работает нормально. Но я хотел бы знать, почему это происходит. Почему конвертер получает пустую строку вместо ожидаемого с плавающей точкой, и только когда мы переключаем модели представления?

Любая идея будет принята с благодарностью. Заранее спасибо... Джо

Ответы [ 2 ]

3 голосов
/ 10 сентября 2013

У меня была такая же проблема (с перечислением вместо числа с плавающей запятой).

Когда представление закрыто, выбор ComboBox очищается.Вы можете проверить это, обработав событие SelectionChanged и проверив коллекцию SelectionChangedEventArgs RemovedItems.Это заканчивается тем, что String.Empty передается в ваш ValueConverter.

В моем случае я изменил ValueConverter.Convert, чтобы позволить string.Empty в качестве допустимого значения, и возвращаю string.Empty.Это код, который я использовал:

// 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 голос
/ 30 марта 2012

Попробуйте

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

Тогда

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

и

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

Тогда вам нужен только один DataTemplate

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

Я предполагаю, что странное значение, передаваемое преобразователю, связано с переключением шаблонов данных.

Не тестировалось

...