Смена картинки при нажатии кнопки. Кнопок много

68
20 марта 2022, 10:00

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

Пример кода

Разметка XAML:

<Window x:Class="TestWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:TestWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style x:Key="image">
            <Style.Triggers>
                <DataTrigger Binding="{Binding IsFirstImage}" Value="False">
                    <Setter Property="Image.Source" Value="Resources/image1.png" />
                </DataTrigger>
                <DataTrigger Binding="{Binding IsFirstImage}" Value="True">
                    <Setter Property="Image.Source" Value="Resources/image2.png" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Border>
        <Canvas>
            <Button x:Name="button1" Width="41" Height="38" Opacity="1" Command="{Binding ToggleCommand}" Canvas.Left="150" Canvas.Top="146">
                <Image x:Name="Qwe1" Style="{StaticResource ResourceKey=image}" Width="41" Height="37"/>
            </Button>
            <Button x:Name="button2" Width="41" Height="38" Opacity="1" Command="{Binding ToggleCommand}" Canvas.Left="100" Canvas.Top="146">
                <Image x:Name="Qwe2" Style="{StaticResource ResourceKey=image}" Width="41" Height="37"/>
            </Button>
            <Button x:Name="button3" Width="41" Height="38" Opacity="1" Command="{Binding ToggleCommand}" Canvas.Left="200" Canvas.Top="146">
                <Image x:Name="Qwe3" Style="{StaticResource ResourceKey=image}" Width="41" Height="37"/>
            </Button>
        </Canvas>
    </Border>
</Window>

И моя VM:

namespace TestWPF
{
    class ViewModel : INotifyPropertyChanged
    {
        private bool isFirstImage;
        public bool IsFirstImage
        {
            get { return isFirstImage; }
            set { isFirstImage = value; OnPropertyChanged("IsFirstImage"); }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName]string prop = "")
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
        private RelayCommand toggleCommand;
        public RelayCommand ToggleCommand
        {
            get
            {
                return toggleCommand ??
                  (toggleCommand = new RelayCommand(obj =>
                  {
                      IsFirstImage = !IsFirstImage;
                  }));
            }
        }
    }
}

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

Answer 1
  • Если элементов много, то это коллекция/массив.
  • Если коллекция изменяется (удаляются/добавляются элементы), то это BindingList или ObservableCollection, ибо они имеют реализацию INotifyCollectionChanged.
  • Если коллекция статична, то можно использовать хоть массив.

Исходя из этого вы можете сделать следующее:

  1. Создаем коллекцию:

    public ObservableCollection<ViewModel> Items { get; }
    

    ViewModel - это ваш класс со свойствами для кнопки.
    get; - нам по сути надо получить только из нее элементы, не инициализируя ее постоянно, по этому тут нет set;.

  2. Инициализируем коллекцию и заполняем.

    public MainViewModel()
    {
        Items = new ObservableCollection<ViewModel>
        {
            new ViewModel() //Задаем нужные свойства если надо.
        };
    }
    

    Вы в любой момент можете написать Items.Add(...).

  3. Переделываем XAML:

    Меняем

    <Canvas>
        <Button x:Name="button1" Width="41" Height="38" Opacity="1" Command="{Binding ToggleCommand}" Canvas.Left="150" Canvas.Top="146">
            <Image x:Name="Qwe1" Style="{StaticResource ResourceKey=image}" Width="41" Height="37"/>
        </Button>
        <Button x:Name="button2" Width="41" Height="38" Opacity="1" Command="{Binding ToggleCommand}" Canvas.Left="100" Canvas.Top="146">
            <Image x:Name="Qwe2" Style="{StaticResource ResourceKey=image}" Width="41" Height="37"/>
        </Button>
        <Button x:Name="button3" Width="41" Height="38" Opacity="1" Command="{Binding ToggleCommand}" Canvas.Left="200" Canvas.Top="146">
            <Image x:Name="Qwe3" Style="{StaticResource ResourceKey=image}" Width="41" Height="37"/>
        </Button>
    </Canvas>
    

    На

    <ItemsControl ItemsSource="{Binding Items}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Button Width="41" Height="38" Command="{Binding ToggleCommand}">
                    <Image Style="{StaticResource ResourceKey=image}" Width="41" Height="37"/>
                </Button>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    Тут как видите все лишнее убрано, используется ItemsControl, который привязан к коллекции Items. В качестве ItemsPanel задан WrapPanel (тут смотрите сами, как кнопки будут размещаться). Также переопределен шаблон ItemTemplate, в него переехала ваша кнопка.

Вот собственно и все, у вас должно будет вывести всю коллекцию Items с заданным шаблоном.

P.S. Код писал на коленке, так что могут быть неточности.

READ ALSO
Как реализовать метод ToType интерфейса IConvertable?

Как реализовать метод ToType интерфейса IConvertable?

Я бы хотел увидеть пример реализации для любой пользовательской структуры

170
Сделать свечение только у индикатора в progress bar wpf c#

Сделать свечение только у индикатора в progress bar wpf c#

Я делаю свечение через DropShadowEffectЕсли тупо повесить его на прогресс бар, свечение будет на всем прогресс баре

239
Одновременный запуск одной формы в приложении

Одновременный запуск одной формы в приложении

Есть форма, которая делает api запрос через таймерМне нужно запустить несколько таких форм, чтобы они делали api запрос

174
TCPClient async/await C#

TCPClient async/await C#

У меня есть несколько девайсовПрограмма должно эти устройства постоянно пинговать

70