Лучше ли поместить мои логики c в обработчик событий или в установщик для MVVM (причуды Xamarin `Picker`` SelectedItem`) - PullRequest
2 голосов
/ 22 января 2020

РЕШЕНИЕ В РЕДАКЦИИ ПРИНЯТОГО ОТВЕТА

У меня есть представление, в котором есть два средства выбора, мне нужно иметь его, чтобы при изменении свойства SelectedItem в одном устройстве выбора список Предметов во втором Picker (ItemSource) также изменяется.

В настоящее время я связал свойства SelectedItem и SelectedIndex обоих сборщиков со свойствами в моем ViewModel. В установщике (ях) для них обоих я выполняю логи c, необходимые для изменения списка предметов во втором сборщике. Список Предметов во втором средстве выбора успешно изменяется, но когда я устанавливаю SelectedIndex (чтобы он выбрал Предмет по умолчанию), это терпит неудачу, если индекс, на который я его устанавливаю, совпадает с индексом, которым он был в предыдущем списке. Вместо этого он показывает Title сборщика, эта проблема, похоже, связана с этой ошибкой .

Мой код:

  • Оба сборщика связаны к наблюдаемой коллекции строк FYI
  • FrameType и DirectionType являются Enums
  • Первоначально я использовал только свойство SelectedItem

Соответствующий XAML

<Label Grid.Row="1" Grid.Column="0" VerticalTextAlignment="Center"
     Text="Direction: " />
     <Picker x:Name="PickerDirection" Grid.Row="1" Grid.Column="1"
                      Title="Select Direction"
                      ItemsSource="{Binding Directions}"
                      SelectedItem="{Binding SelectedDirection}"
                      SelectedIndex="{Binding SelectedDirectionIndex}"></Picker>

<Label Grid.Row="2" Grid.Column="0" VerticalTextAlignment="Center"
       Text="Frame: "/>
<Picker x:Name="PickerFrame" Grid.Row="2" Grid.Column="1"
                 Title="Select Frame"
                 ItemsSource="{Binding Frames}"
                 SelectedItem="{Binding SelectedFrame}"
                 SelectedIndex="{Binding SelectedFrameIndex}"></Picker>

Соответствующий вид Код модели:

public string SelectedDirection
{
    get { return _selectedDirection; }
    set 
    {
        if (Directions.Contains(value))
        {
            if (_selectedDirection != value)
            {
                if (EnumUtils.ToEnumFromString<FrameType>(SelectedFrame) == FrameType.Road &&
                            !DirectionsRoad.Contains(value))
                {
                 _selectedDirection = Directions[Directions.IndexOf(DirectionType.Right.ToString())];
                }
                else
                {
                    _selectedDirection = value;
                }
                OnPropertyChanged();
            }
        }
    }
}

public string SelectedFrame
{
    get { return _selectedFrame; }
    set
    {
        if (_selectedFrame != value)
        {
            _selectedFrame = value;
            if (EnumUtils.ToEnumFromString<FrameType>(_selectedFrame) == FrameType.Road)
            {
                Directions = DirectionsRoad;
                if (Directions.Contains(SelectedDirection))
                {
                    SelectedDirectionIndex = Directions.IndexOf(SelectedDirection);
                }
                else
                {
                     SelectedDirectionIndex = Directions.IndexOf(DirectionType.Right.ToString());
                }
              }else if (EnumUtils.ToEnumFromString<FrameType>(_selectedFrame) == FrameType.Lane)
              {
                  Directions = DirectionsAll;
                  SelectedDirectionIndex = Directions.IndexOf(SelectedDirection);
              }
               OnPropertyChanged();
           }
       }
   }
}

public int SelectedDirectionIndex
{
    get { return _selectedDirectionIndex; }
    set
    {
        if (_selectedDirectionIndex != value)
        {
            if (!(value < 0 || value >= Directions.Count))
            {
                _selectedDirectionIndex = value;
                OnPropertyChanged();
            }
        }
    }
}

public int SelectedFrameIndex
{
    get { return _selectedFrameIndex; }
    set
    {
        if (_selectedFrameIndex != value)
        {
            if (!(value < 0 || value >= Frames.Count))
            {
                _selectedFrameIndex = value;
                OnPropertyChanged();
            }
        }
    }
}


Результат: enter image description here

Я ожидаю, что он никогда не будет пустым, так как я убедитесь, что SelectedDirection всегда установлен на что-то.

Примечания:

  • Сначала я использовал только свойство SelectedItem, но когда столкнулся эта ошибка, я думал, что использование свойства SelectedIndex поможет исправить это.

  • Я использовал ObservableCollection для обеспечения согласованности с другими viewModels в проекте и для обеспечения того, чтобы параметры в окне выбора обновляются при внесении изменений (исходя из моего понимания, вам нужно использовать ObservableCollection, чтобы сделать эту возможность le).

  • Я планирую преобразовать код в установщике для SelectedFrame в более мелкие функции, как только я получу работу.

Из-за этого кажется, что использование SelectedIndexChanged события Picker было бы единственным способом исправить это. Однако комментарий ottermati c в этом вопросе говорит, что события ненадежны . Поэтому я чувствовал, что лучше было бы выполнить эту логику c в установщике.

Если кто-то может прокомментировать, что я могу делать неправильно в моем коде, который вызывает эту проблему, а также прокомментировать плюсы и минусы и / или должен ли я использовать eventHandler или иметь лог c в моем сеттере. Спасибо

Ответы [ 2 ]

2 голосов
/ 22 января 2020

На самом деле я не вижу , почему вы используете SelectedItem и SelectedIndex, но я думаю, что то, чего вы пытаетесь достичь, может быть достигнуто легче.

все, я не думаю, что вам нужно ObservableCollection типов в вашем примере, так как вы все равно устанавливаете направления и не изменяете коллекцию. Более того, насколько я могу судить, возиться с индексами совершенно не нужно. В любом случае вы используете строки, и хотя String не является типом значения, но является ссылочным типом, вы практически не можете различить guish два String экземпляра, которые имеют одинаковое содержимое, следовательно, присваивая соответствующие значения SelectedDirection и SelectedFrame достаточно.

Следующие проверки кажутся мне излишними

if (Directions.Contains(value))


if (EnumUtils.ToEnumFromString<FrameType>(SelectedFrame) == FrameType.Road &&
    !DirectionsRoad.Contains(value))

, поскольку Directions в любом случае установлено на DirectionsRoad, если SelectedFrame установлено на "Road". Следовательно, я бы предположил, что второе условие не оценивается как true в любом случае. Следовательно, SelectedDirection можно упростить:

public string SelectedDirection
{
    get => _selectedDirection;
    set 
    {
        if (_selectedDirection != value && Directions.Contains(value))
        {
            _selectedDirection = value;
            OnPropertyChanged();
        }
    }
}

В установщике SelectedFrame происходит много вещей, которые я бы рефакторировал для методов самостоятельно, чтобы улучшить ясность.

public string SelectedFrame
{
    get => _selectedFrame;
    set
    {
        if (_selectedFrame != value)
        {
            _selectedFrame = value;
            UpdateAvailableDirections();
            OnPropertyChanged();
        }
    }
}

private void UpdateAvailableDirections()
{
    // store the selected direction
    var previouslySelectedDirection = SelectedDirection;

    Directions = GetValidDirectionsByFrameType(EnumUtils.ToEnumFromString<FrameType>(SelectedFrame));
    SelectedDirection = GetSelectedDirection(previoslySelectedDirection, Directions);
}

private string[] GetValidDirectionsByFrameType(FrameType frameType)
{
    return frameType == FrameType.Road ? DirectionsRoad : DirectionsAll; 
}

private string GetSelectedDirection(string previouslySelectedDirection, string[] validDirections)
{
    return validDirections.Contains(previouslySelectedDirection) ? previouslySelectedDirection : DefaultDirection;
}

При установке SelectedItem вместо работы с индексами должны отображаться правильные значения.

Что касается вашего вопроса, может ли эта логика c лучше подходить в обработчике событий или в установщике, зависит от ваших требований. Если вам нужен только индекс, событие SelectedIndexChanged может сработать для вас, но если значение требуется в нескольких местах и ​​методах, которые не вызываются обработчиком события, представленное решение может быть более жизнеспособным.

Edit

Вы были правы, это не имеет никакого отношения к использованию SelectedIndex и SelectedItem. Вопрос немного более тонкий.

Я построил быстрое подтверждение концепции и нашел следующее:

  • Предполагая, что SelectedDirection равно "Right" (и индекс установлен соответственно)
  • Когда установлено Directions, SelectedItem на сборщике сбрасывается
  • SelectedDirection установлено на null
  • this.Directions.Contains(value) оценивается на false, следовательно _selectedDirection не установлено (это верно для SelectedDirectionIndex, поскольку значение -1 фильтруется с помощью if(!value < 0 || value >= this.Directions.Count))
  • Когда впоследствии устанавливается SelectedDirection, значение по-прежнему равно "Right", следовательно, OnPropertyChanged не вызывается (так как значения одинаковы), а SelectedItem не установлено

Таким образом, существует несоответствие между значением, фактически сохраненным Picker, и свойством в модель представления, которая приводит к неожиданному поведению.

Как смягчить проблему?

Я бы все равно придерживался кода без индексов (если они вам действительно не нужны) и использовал бы строковые значения .

Есть и другие возможности, но я бы изменил установщик на SelectedDirection. Когда вы позволили установить значение * 10 80 *, PropertyChanged будет повышаться должным образом, если впоследствии будет установлено значение Right. Если вам действительно нужно отфильтровать, какое значение установлено, вы все равно должны увеличить OnPropertyChanged, чтобы сообщить Picker, что значение изменилось (предотвращая несоответствие между фактическим значением Picker s и моделью представления)

public string SelectedDirection
{
    get => _selectedDirection;
    set 
    {
        if (_selectedDirection != value)
        {
            if(Directions.Contains(value))
            {
                _selectedDirection = value;
            }
            else
            {
                _selectedDirection = DirectionTypes.Right.ToString();
            }

            OnPropertyChanged();
        }
    }
}
0 голосов
/ 22 января 2020

Обнаружено несколько хакерских исправлений для этого, и, похоже, проблема Xamarin. Я сделал следующие изменения в своем коде: "

Соответствующие изменения в установщике для SelectedFrame:

public string SelectedFrame
{
    get { return _selectedFrame; }
    set
    {
        if (_selectedFrame != value)
        {
            _selectedFrame = value;
            if (EnumUtils.ToEnumFromString<FrameType>(_selectedFrame) == FrameType.Road)
            {
                Directions = DirectionsRoad;
                if (Directions.Contains(SelectedDirection))
                {
                   /*Relevant edits*/
                    var position = Directions.IndexOf(SelectedDirection);
                    SelectedDirection = Directions[Directions.Count - position -1];
                    SelectedDirection = Directions[position];
                }
                else
                {
                     SelectedDirectionIndex = Directions.IndexOf(DirectionType.Right.ToString());
                }
              }else if (EnumUtils.ToEnumFromString<FrameType>(_selectedFrame) == FrameType.Lane)
              {
                  Directions = DirectionsAll;
                  /*Relevant edits*/
                  var position = Directions.IndexOf(SelectedDirection);
                  SelectedDirection = Directions[Directions.Count - position -1];
                  SelectedDirection = Directions[position];
              }
               OnPropertyChanged();
           }
       }
   }
}

Кажется, что моя проблема возникает, когда я изменяю содержимое моего ObservableCollectoin, но SelectedDirection остается прежним.

Когда я изменяю Directions (что является ObservableCollection), присваивая его DirectionsAll (также ObservableCollection), мне нужно убедитесь, что SelectedDirection изменяется, добавленный код гарантирует, что изменение действительно произойдет до SelectionDirection и это исправит. Кажется несколько странным, но это работает.

Результат:

Fix video

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...