Обновление пользовательского интерфейса при изменении коллекций

Завершено

В этом уроке пользователь выберет любимые цвета. Доступные цвета перечислены в раскрывающемся списке (элемент управления ComboBox). Пользователь выбирает цвет и добавляет его в избранное, нажав кнопку. Любимые цвета показаны ниже. При выборе любимого цвета также отображается кнопка, которая позволяет пользователю удалить выбранный цвет из избранного.

Screenshot of sample databinding app running and displaying favorite colors.

Сначала предоставьте возможность выбрать цвет из коллекции LotsOfColors и добавить его в список избранных цветов.

1. Создание SelectedColor свойства

Начнем с кода, а затем перейдем к пользовательскому интерфейсу.

Нам нужен способ, чтобы определить, какой элемент (то есть экземпляр класса ColorDescriptor) выбрал пользователь из раскрывающегося списка. Элемент управления ComboBox имеет свойство SelectedItem, которое получает и задает текущий выбранный элемент. Таким образом, мы можем привязать это свойство к свойству типа ColorDescriptor в нашем коде.

Откройте ColorListLogic.cs и добавьте следующий код:

private ColorDescriptor _selectedColor;

public ColorDescriptor SelectedColor
{
    get => _selectedColor;
    set => Set(ref _selectedColor, value);
}

Этот шаблон уже должен быть вам знаком. Это стандартное свойство, дополненное механизмом INotifyPropertyChanged с использованием базового класса ObservableObject.

2. Создание FavoriteColors списка

В списке FavoriteColors хранятся цвета, выбранные пользователем в качестве избранных. Это простое свойство.

public List<ColorDescriptor> FavoriteColors { get; } = 
    new List<ColorDescriptor>();

3. Добавление выбранного цвета в избранное

Добавление выбранного цвета в избранное происходит в методе AddSelectedColorToFavorites.

public void AddSelectedColorToFavorites()
{
    FavoriteColors.Add(SelectedColor);
}

Предполагается, что при вызове этого метода SelectedColor заполняется цветом, который должен быть добавлен в список избранных.

На этом работа с кодом закончена (пока). Давайте обратим внимание на XAML.

4. Изменение на ListBoxComboBox

Так как мы хотим иметь полный список цветов, отображаемых в раскрывающемся списке (который является элементом ComboBox управления), нам нужно изменить XAML. К счастью, и ListBox, и ComboBox являются потомками элемента управления ItemsControl и работают похоже, несмотря на то, что есть различия в том, как они выглядят и функционируют. Нам нужно всего лишь заменить ListBox на ComboBox в файле ColorList.xaml. Для этого можно использовать команду Правка>Найти и заменить>Быстрая замена (CTRL+H).

Screenshot of Visual Studio showing the Quick Replace command.

Если вы сейчас запустите приложение, то увидите, что ListBox заменен на ComboBox, но цвета по-прежнему отображаются с использованием того же шаблона.

Screenshot of favorite colors app showing the color selection combo box.

5. Извлечение шаблона в ресурс

Вам нужно будет повторно использовать шаблон для списка любимых цветов позже. Советуем хранить шаблон в одном расположении, чтобы улучшить читаемость XAML. Важнее то, что в этом случае изменения шаблона влияют на каждый экземпляр. Давайте извлечем шаблон в ресурс.

Для любого элемента FrameworkElement может быть определен свой список ресурсов. Мы решили сделать наш шаблон глобальным для этой страницы, поэтому давайте добавим тег <Page.Reources> над элементом <Grid>. Затем переместим в него весь тег <DataTemplate> и его содержимое.

<Page.Resources>
    <DataTemplate x:DataType="local:ColorDescriptor">
        <StackPanel Orientation="Horizontal">
            <Rectangle Width="30" 
                       Height="30">
                <Rectangle.Fill>
                    <SolidColorBrush Color="{x:Bind Color}"/>
                </Rectangle.Fill>
            </Rectangle>
            <TextBlock Text="{x:Bind Name}" 
                       Margin="20, 10, 0, 10"/>
        </StackPanel>
    </DataTemplate>
</Page.Resources>

Visual Studio предупреждает о том, что объекты в теге <Page.Resources> (который является IDictionary) должны иметь атрибут Key, поэтому добавьте его в <DataTemplate>.

<DataTemplate x:Key="ColorTemplate" 
              x:DataType="local:ColorDescriptor">

Этот ключ позволяет нам ссылаться на этот шаблон из другого расположения на странице, например на шаблон ComboBox.ItemTemplate, который потерял свое содержимое. Чтобы ComboBox использовал ресурс ColorDescriptor, можно удалить тег <ComboBox.ItemTemplate> и указать его в качестве атрибута внутри самого тега <ComboBox>. Весь тег <ComboBox> выглядит следующим образом:

<ComboBox ItemsSource="{x:Bind Logic.LotsOfColors}" 
          Margin="20" 
          Width="200"
          HorizontalAlignment="Left" 
          VerticalAlignment="Top"
          ItemTemplate="{StaticResource ColorTemplate}"/>

Вы можете снова запустить приложение, чтобы убедиться, что шаблон элемента работает.

6. Создание остальной части пользовательского интерфейса

Пользовательский интерфейс прост. Все элементы управления (раскрывающийся список, кнопка Добавить в избранное, список избранных (с текстом заголовка) и кнопка Удалить из избранного) размещаются в одном вертикальном элементе StackPanel. Замените все содержимое элемента <Grid> следующим:

<StackPanel>
    <ComboBox ItemsSource="{x:Bind Logic.LotsOfColors}" 
              Margin="20, 20, 20, 0" 
              Width="200"
              HorizontalAlignment="Left" 
              VerticalAlignment="Top"
              ItemTemplate="{StaticResource ColorTemplate}"
              SelectedItem="{x:Bind Logic.SelectedColor, Mode=TwoWay}" 
              />

    <Button Margin="20" 
            Click="{x:Bind Logic.AddSelectedColorToFavorites}">Add to Favorites</Button>
    <TextBlock FontSize="25" 
               Margin="20, 20, 20, 0">Favorite colors</TextBlock>

    <ListBox ItemsSource="{x:Bind Logic.FavoriteColors}"
             ItemTemplate="{StaticResource ColorTemplate}"
             Margin="20, 20, 20, 0"/>

    <Button Margin="20">Remove from Favorites</Button>
</StackPanel>

Существует привязка TwoWay между текущим выбранным элементом в ComboBox и свойством SelectedColor в классе ColorListLogic.

7. Запуск приложения

Запустите приложение сейчас, выберите цвет из ComboBox и нажмите кнопку Добавить в избранное. Ничего не происходит. Добавление точки останова в конец метода AddSelectedColorToFavorites в классе ColorListLogic показывает, что код работает. Выбранный цвет добавляется в список FavoriteColors.

Причина, по которой пользовательский интерфейс не отражает изменения в элементе List<ColorDescriptor>, заключается в том, что интерфейс должен получать уведомление при изменении коллекции. Для списков это делается через интерфейс System.Collections.Specialized.INotifyCollectionChanged. К счастью, нам не нужно это реализовывать. В классе System.Collections.ObjectModel.ObservableCollection<T> уже есть все, что нам нужно.

Чтобы наше приложение работало, нам нужно лишь использовать для свойства FavoriteColors класс ObservableCollection<T> вместо класса List<T>.

public ObservableCollection<ColorDescriptor> FavoriteColors { get; } = 
    new ObservableCollection<ColorDescriptor>();

Если вы запустите приложение сейчас, то выбор цветов из раскрывающегося списка и нажатие кнопки Добавить в избранное будут работать правильно. Выбранные цвета будут отображены в элементе ListBox. Отлично!

Screenshot of sample app showing favorite colors added to list.

8. Избегайте добавления пустых элементов

При запуске приложения в раскрывающемся списке не выбран цвет. Если вы сейчас нажмете кнопку Add to Favorites (Добавить в избранное), в список будут добавлены значения null. Это ошибка, так что давайте исправим ее.

Мы могли бы добавить проверку null в метод AddSelectedColorToFavorites, но это не помешает кнопке Добавить в избранное отображаться, когда она не является функциональной. Вместо этого давайте сделаем так, чтобы в раскрывающемся списке всегда был выбран элемент. Так как свойство SelectedItem раскрывающегося списка имеет двустороннюю привязку к свойству SelectedColor в коде, давайте просто инициализируем его допустимым значением при запуске. Добавьте следующую строку в конце конструктора ColorListLogic:

SelectedColor = LotsOfColors[0];

Это гарантирует, что первый элемент списка LotsOfColors будет выбран при запуске приложения. Пользователь не сможет добавить null в коллекцию FavoriteColors.

9. Удаление избранных цветов

Далее следует добавить возможность удалять избранные цвета из элемента ListBox. Это происходит, когда пользователь выбирает элемент в ListBox и нажимает кнопку Remove from Favorites (Удалить из избранного).

Подобно тому, как работал ComboBox, мы можем отслеживать элемент, выбранный пользователем в ListBox, через его свойство SelectedItem. Мы можем привязать это к свойству в коде. Добавьте это в класс ColorListLogic.

private ColorDescriptor _selectedFavoriteColor;

public ColorDescriptor SelectedFavoriteColor
{
    get => _selectedFavoriteColor;
    set
    {
        Set(ref _selectedFavoriteColor, value);
        RaisePropertyChanged(nameof(IsRemoveFavoriteColorButtonVisible));
    }
}

public bool IsRemoveFavoriteColorButtonVisible => SelectedFavoriteColor != null;

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

Чтобы реально выполнить удаление цвета из списка избранного, нужно написать еще один метод.

public void RemoveFavoriteColor()
{
    FavoriteColors.Remove(SelectedFavoriteColor);
}

Чтобы подключить кнопку в коде XAML, откройте файл ColorList.xaml и измените код XAML кнопки Удалить из избранного Измените ее таким образом, чтобы она включает привязку Visibility , а также привязку Click .

<Button Margin="20" 
        Visibility="{x:Bind Logic.IsRemoveFavoriteColorButtonVisible, Mode=OneWay}"
        Click="{x:Bind Logic.RemoveFavoriteColor}">Remove from Favorites</Button>

Осталось только привязать свойство SelectedItem элемента ListBox к свойству Logic.SelectedFavoriteColor. Добавьте атрибут SelectedItem в ListBox в коде XAML.

<ListBox SelectedItem="{x:Bind Logic.SelectedFavoriteColor, Mode=TwoWay}"... >

Запустите приложение сейчас и убедитесь, что вы можете добавлять цвета в список избранных, а также удалять их. Обратите внимание, как кнопка Удалить из избранного появляется и исчезает в зависимости от того, выбран ли избранный цвет, который можно удалить.

Итоги

В этом уроке показано, как получить и установить выбранный элемент в элементе управления ComboBox или ListBox, связав его свойства SelectedItem со свойством C#. Вы также видели, как при использовании ObservableCollection в коде пользовательский интерфейс автоматически обновляет содержимое элементов управления, отображающих несколько элементов.

На этом занятии пользователь выберет свои любимые цвета. Доступные цвета перечислены в раскрывающемся списке (элемент управления ComboBox). Пользователь выбирает цвет и добавляет его в избранное, нажав кнопку. Избранные цвета отображаются под полным списком. При выборе любимого цвета также отображается кнопка, которая позволяет пользователю удалить выбранный цвет из избранного.

Screenshot of sample app showing selected favorite color with remove button available.

Сначала мы добавим возможность выбрать цвет из LotsOfColors коллекции и добавить его в список любимых цветов.

1. Создание SelectedColor свойства

Начнем с кода, а затем мы будем работать с изменениями пользовательского интерфейса.

Нам нужен способ, чтобы определить, какой элемент (то есть экземпляр класса ColorDescriptor) выбрал пользователь из раскрывающегося списка. Элемент управления ComboBox имеет свойство SelectedItem, которое получает и задает текущий выбранный элемент. Таким образом, мы можем привязать это свойство к свойству типа ColorDescriptor в нашем коде.

Откройте ColorListDataContext.cs и добавьте следующий код:

private ColorDescriptor? _selectedColor;

public ColorDescriptor? SelectedColor
{
    get => _selectedColor;
    set => Set(ref _selectedColor, value);
}

Этот шаблон уже должен быть вам знаком. Это стандартное свойство, которое использует INotifyPropertyChanged механизм с помощью ObservableObject базового класса.

2. Создание FavoriteColors списка

В списке FavoriteColors хранятся цвета, которые пользователь выбрал в качестве избранных. Это простое свойство.

public List<ColorDescriptor> FavoriteColors { get; } = 
    new List<ColorDescriptor>();

3. Добавление выбранного цвета в избранное

Добавление выбранного цвета в избранное происходит в методе AddSelectedColorToFavorites. В качестве меры предосторожности проверка, если SelectedColor это свойствоnull. Если это так, вернитесь из метода. В противном случае добавьте выбранный цвет в FavoriteColors список.

public void AddSelectedColorToFavorites()
{
    if (SelectedColor == null) return;
    FavoriteColors.Add(SelectedColor);
}

При вызове SelectedColor этого метода следует заполнить цветом, добавленным в список избранного, но лучше не делать предположений.

На этом работа с кодом закончена (пока). Давайте обратим внимание на XAML.

4. Изменение на ListBoxComboBox

Так как мы хотим иметь полный список цветов, отображаемых в раскрывающемся списке (который является элементом ComboBox управления), нам нужно изменить XAML. К счастью, оба ListBox и ComboBox являются потомками ItemsControl контроля, и они работают аналогично, несмотря на то, что существуют многочисленные различия в том, как они выглядят и ведут себя. Все, что нам нужно сделать, — заменить ListBox его ComboBox в ColorList.xaml файле. Для этого можно использовать команду Правка>Найти и заменить>Быстрая замена (CTRL+H).

Repeat screenshot of Visual Studio showing Quick Replace command.

Если вы сейчас запустите приложение, то увидите, что ListBox заменен на ComboBox, но цвета по-прежнему отображаются с использованием того же шаблона.

Screenshot of sample app showing color list in a ComboBox.

5. Извлечение шаблона в ресурс

Вам нужно будет повторно использовать шаблон для списка любимых цветов позже. Советуем хранить шаблон в одном расположении, чтобы улучшить читаемость XAML. Важнее то, что в этом случае изменения шаблона влияют на каждый экземпляр. Давайте извлечем шаблон в ресурс.

Для любого элемента FrameworkElement может быть определен свой список ресурсов. Мы решили сделать наш шаблон глобальным для всего элемента Window, поэтому давайте добавим тег <Window.Reources> над элементом <Grid>. Затем переместим в него весь тег <DataTemplate> и его содержимое.

<Window.Resources>
    <DataTemplate x:Key="ColorTemplate">
        <StackPanel Orientation="Horizontal">
            <Rectangle Width="80" 
                       Height="20">
                <Rectangle.Fill>
                    <SolidColorBrush Color="{Binding Color}"/>
                </Rectangle.Fill>
            </Rectangle>
            <TextBlock Text="{Binding Name}" 
                       Margin="20, 10, 0, 10"/>
        </StackPanel>
    </DataTemplate>
    <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>

<Window.Resources> является словарем, поэтому каждая запись также должна иметь ключ. Мы добавили его в <DataTemplate>.

<DataTemplate x:Key="ColorTemplate">

Этот ключ позволяет нам ссылаться на этот шаблон из другого расположения в элементе Window, например из элемента ComboBox.ItemTemplate, который потерял свое содержимое. Чтобы ComboBox использовал ресурс ColorDescriptor, можно удалить тег <ComboBox.ItemTemplate> и указать его в качестве атрибута внутри самого тега <ComboBox>. Весь тег <ComboBox> выглядит следующим образом:

<ComboBox ItemsSource="{x:Bind Logic.LotsOfColors}" 
          Margin="20" 
          Width="200"
          HorizontalAlignment="Left" 
          VerticalAlignment="Top"
          ItemTemplate="{StaticResource ColorTemplate}"/>

Вы можете снова запустить приложение, чтобы убедиться, что шаблон элемента работает.

6. Создание остальной части пользовательского интерфейса

Пользовательский интерфейс прост. Все элементы управления (раскрывающийся список, кнопка "Добавить в избранное", список избранного (с текстом заголовка) и кнопка "Удалить из избранного") вложены в одну вертикальную.StackPanel Замените весь элемент <Grid> следующим кодом:

<StackPanel>
    <ComboBox ItemsSource="{Binding LotsOfColors}" 
              Margin="20, 20, 20, 0" 
              Width="200"
              HorizontalAlignment="Left" 
              VerticalAlignment="Top"
              ItemTemplate="{StaticResource ColorTemplate}"
              SelectedItem="{Binding SelectedColor, Mode=TwoWay}" />

    <Button 
        Margin="20" 
        HorizontalAlignment="Left">Add to Favorites</Button>

    <TextBlock
            FontSize="25" 
            Margin="20, 20, 20, 0">Favorite colors</TextBlock>

    <ListBox ItemsSource="{Binding FavoriteColors}"
             ItemTemplate="{StaticResource ColorTemplate}"
             Margin="20, 20, 20, 0"
             HorizontalAlignment="Left"/>

    <Button Margin="20" 
            HorizontalAlignment="Left">Remove from Favorites</Button>

</StackPanel>

Существует привязка TwoWay между текущим выбранным элементом в ComboBox и свойством SelectedColor в классе ColorListDataContext.

7. Создание обработчика нажатия кнопки Add to Favorites

Мы уже реализовали логику добавления выбранного цвета в список FavoriteColors в методе AddSelectedColorToFavorites. Однако нам нужно, чтобы этот метод вызывался при нажатии кнопки.

Дважды нажмите кнопку Add to Favorites в визуальном конструкторе Visual Studio. При этом создается Button_Click метод в коде программной части и ссылка на него в Click событии в XAML:

<Button ... Click="Button_Click">Add to Favorites</Button>

Переименуйте метод Button_Click в AddToFavorites_Click как в коде программной части, так и в XAML. Это можно сделать, переименовав его в коде, а затем используя значок отвертки, выбрав "Переименовать Button_Click в AddToFavorites_Click". Это также позволит изменить имя метода в файле XAML.

Screenshot of Visual Studio quick action option to rename button click method..

Добавьте свойство для удобства в начало класса ColorList, чтобы упростить доступ к классу ColorListDataContext.

private ColorListDataContext DC => (ColorListDataContext)DataContext;

Затем в методе AddToFavorites_Click вызовите ранее записанную логику из класса ColorListDataContext:

private void AddToFavorites_Click(object sender, RoutedEventArgs e)
{
    DC.AddSelectedColorToFavorites();
}

8. Запуск приложения

Запустите приложение сейчас, выберите цвет из ComboBox и нажмите кнопку Добавить в избранное. Ничего не происходит. Добавление точки останова в конец метода AddSelectedColorToFavorites в классе ColorListDataContext показывает, что код работает. Выбранный цвет добавляется в список FavoriteColors.

Причина, по которой пользовательский интерфейс не отражает изменения в элементе List<ColorDescriptor>, заключается в том, что интерфейс должен получать уведомление при изменении коллекции. Для списков это делается через интерфейс System.Collections.Specialized.INotifyCollectionChanged. К счастью, нам не нужно это реализовывать. В классе System.Collections.ObjectModel.ObservableCollection<T> уже есть все, что нам нужно.

Чтобы наше приложение работало, нам нужно лишь использовать для свойства FavoriteColors класс ObservableCollection<T> вместо класса List<T>.

public ObservableCollection<ColorDescriptor> FavoriteColors { get; } = 
    new ObservableCollection<ColorDescriptor>();

Если вы запустите приложение сейчас, то выбор цветов из раскрывающегося списка и нажатие кнопки Добавить в избранное будут работать правильно. Выбранные цвета будут отображены в элементе ListBox. Отлично!

Screenshot of sample app showing selected color added to favorites.

9. Избегайте добавления пустых элементов

При запуске приложения в раскрывающемся списке не выбран цвет. В настоящее время у нас есть null проверка в AddSelectedColorToFavorites методе, чтобы предотвратить добавление элементов NULL в список при нажатии кнопки "Добавить в избранное". Давайте изменим это, чтобы убедиться, что в раскрывающемся списке всегда выбран цвет.

Проверка null не препятствует отображению кнопки "Добавить в избранное", когда она не работает. Итак, давайте просто убедитесь, что есть всегда выбранный элемент. Так как свойство SelectedItem раскрывающегося списка имеет двустороннюю привязку к свойству SelectedColor в коде, давайте просто инициализируем его допустимым значением при запуске. Добавьте следующую строку в конце конструктора ColorListDataContext:

SelectedColor = LotsOfColors[0];

Это гарантирует, что первый элемент списка LotsOfColors будет выбран при запуске приложения. Пользователь не сможет добавить null в коллекцию FavoriteColors.

10. Удаление избранных цветов

Далее следует добавить возможность удалять избранные цвета из элемента ListBox. Это происходит, когда пользователь выбирает элемент в ListBox и нажимает кнопку Удалить из избранного.

Подобно тому, как работал ComboBox, мы можем отслеживать элемент, выбранный пользователем в ListBox, через его свойство SelectedItem. Мы можем привязать это к свойству в коде. Добавьте новое свойство в ColorListDataContext класс следующим образом:

private ColorDescriptor? _selectedFavoriteColor;

public ColorDescriptor? SelectedFavoriteColor
{
    get => _selectedFavoriteColor;
    set
    {
        Set(ref _selectedFavoriteColor, value);
        RaisePropertyChanged(nameof(IsRemoveFavoriteEnabled));
    }
}

public bool IsRemoveFavoriteEnabled => SelectedFavoriteColor != null;

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

Чтобы реально выполнить удаление цвета из списка избранного, нужно написать еще один метод.

public void RemoveFavoriteColor()
{
    if (SelectedFavoriteColor == null) return;
    FavoriteColors.Remove(SelectedFavoriteColor);
}

Затем привяжите свойство SelectedItem элемента ListBox к свойству SelectedFavoriteColor. Добавьте атрибут в SelectedItemListBoxCodeList.xaml.

<ListBox SelectedItem="{Binding SelectedFavoriteColor, Mode=TwoWay}"... >

Чтобы подключить кнопку "Удалить из избранного ", измените XAML кнопки "Удалить из избранного ", чтобы она включает IsEnabled привязку, а также Click обработчик событий.

<Button Margin="20" 
        HorizontalAlignment="Left"
        Click="RemoveFromFavorites_Click"
        IsEnabled="{Binding IsRemoveFavoriteEnabled}">Remove from Favorites</Button>

Для вызова RemoveFromFavoriteColor в логику также необходимо добавить RemoveFromFavorites_Click методColorList.xaml.cs.

private void RemoveFromFavorites_Click(object sender, RoutedEventArgs e)
{
    DC.RemoveFavoriteColor();
}

Запустите приложение сейчас и убедитесь, что вы можете добавлять цвета в список избранных, а также удалять их. Обратите внимание, как кнопка Удалить из избранного включается и отключается в зависимости от того, выбран ли избранный цвет, который можно удалить.

В качестве упражнения попробуйте скрыть всю часть с избранными цветами в пользовательском интерфейсе, когда коллекция FavoriteColors пуста. Подсказка. Используйте StackPanel, чтобы сгруппировать затронутые элементы управления, и привяжите условия видимости StackPanel к свойству в классе ColorListDataContext. При добавлении или удалении избранного цвета уведомляйте пользовательский интерфейс об изменениях этого свойства.

Итоги

В этом уроке показано, как получить и установить выбранный элемент в элементе управления ComboBox или ListBox, связав его свойство SelectedItem со свойством C#. Вы также видели, как при использовании ObservableCollection в коде пользовательский интерфейс автоматически обновляет содержимое элементов управления, отображающих несколько элементов.