Лучше всего будет начать с чтения туториалов по
MVVM
Паттерн Model-View-ViewModel
Ключевое в таком подходе разделение представления (
View) от внутренней логики (
Model/
VM). В некоторых очень простых случаях (например, демонстрационных примерах) роли
Model и
VM объединяют в одну.
Концептуально, то что вы описали в своем вопросе можно определить следующим образом:
Есть некоторый объект, который содержит информацию об изменяемом значении. Также объект характеризуется двумя состояниями: "нажат" (кнопка окрашена в красный цвет) и "отжат" (кнопка стандартного цвета). В терминах C# можно выразить в виде класса:
public class Item : ViewModelBase
{
private int value;
private bool isSelected;
public int Value
{
get => value;
private set => SetProperty(ref this.value, value);
}
public bool IsSelected
{
get => isSelected;
set => SetProperty(ref isSelected, value);
}
public Item(int value, bool isSelected = false)
{
Value = value;
IsSelected = isSelected;
}
public void IncrementSelected()
{
if (IsSelected)
{
Value++;
}
}
}
Обратите внимание что класс унаследован от
ViewModelBase это вспомогательный класс для того чтобы сообщать представлению об изменении некоторых свойств объекта.
Теперь сама страница:
Здесь у вас есть 3 объекта (
Item) которые можно изменять, а также методы для изменения этих самых объектов. Понадобятся всего два метода:
1. Изменить состояние соответствующего объекта по нажатию на кнопку
2. Изменить значение для всех объектов для которых
IsSelected истинно
Опишем этом в коде:
public class MyContext
{
public int BaseValue { get; } = 14;
public Item Item1 { get; }
public Item Item2 { get; }
public Item Item3 { get; }
public MyContext()
{
Item1 = new Item(BaseValue, true);
Item2 = new Item(BaseValue);
Item3 = new Item(BaseValue);
IncrementSelectedCommand =
new DelegateCommand<object>(_ => IncrementSelected());
ClickCommand = new DelegateCommand<Item>(
o => SelectOrDeselect(o),
o => o is Item);
}
public ICommand IncrementSelectedCommand { get; }
public ICommand ClickCommand { get; }
public void IncrementSelected()
{
Item1.IncrementSelected();
Item2.IncrementSelected();
Item3.IncrementSelected();
}
public void SelectOrDeselect(Item item)
{
item.IsSelected = !item.IsSelected;
}
}
Теперь сама сложная часть -
View. Сложная она только тем что я хочу максимально близко оставить вашу разметку.
Для начала определим простой стиль который будет менять цвет фона у кнопки:
<Style x:Key="selectableBtn" TargetType="Button">
<Style.Triggers>
<DataTrigger
Binding="{Binding IsSelected}"
TargetType="Button"
Value="True">
<Setter Property="BackgroundColor" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
Пока все просто - как только
IsSelected становится равным True задаем установку свойства
BackgroundColor в нужный цвет.
Последний рубеж - сетка:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label
Grid.Column="1"
FontSize="Large"
HorizontalTextAlignment="Center"
Text="I"
VerticalTextAlignment="Center" />
<Label
Grid.Column="2"
FontSize="Large"
HorizontalTextAlignment="Center"
Text="II"
VerticalTextAlignment="Center" />
<Label
Grid.Column="3"
FontSize="Large"
HorizontalTextAlignment="Center"
Text="III"
VerticalTextAlignment="Center" />
<Label
Grid.Row="1"
FontSize="Medium"
HorizontalTextAlignment="Center"
Text="{Binding BaseValue}" />
<Button
Grid.Row="1"
Grid.Column="1"
BindingContext="{Binding Item1}"
Command="{Binding BindingContext.ClickCommand, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type d:ContentPage}}}"
CommandParameter="{Binding .}"
Style="{StaticResource selectableBtn}"
Text="{Binding Value}" />
<Button
Grid.Row="1"
Grid.Column="2"
BindingContext="{Binding Item2}"
Command="{Binding BindingContext.ClickCommand, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type d:ContentPage}}}"
CommandParameter="{Binding .}"
Style="{StaticResource selectableBtn}"
Text="{Binding Value}" />
<Button
Grid.Row="1"
Grid.Column="3"
BindingContext="{Binding Item3}"
Command="{Binding BindingContext.ClickCommand, Source={RelativeSource Mode=FindAncestor, AncestorType={x:Type d:ContentPage}}}"
CommandParameter="{Binding .}"
Style="{StaticResource selectableBtn}"
Text="{Binding Value}" />
<Button
Grid.Row="2"
Grid.Column="3"
Command="{Binding IncrementSelectedCommand}"
Text="Add" />
</Grid>
Единственную хитрость здесь представляет установка
BindingContext для кнопки чтобы реагировать на связанный элемент.
И как вам уже подсказали чтобы из
View передать информацию в
VM можно использовать
CommandParameter.
И, завершающий штрих, установка
BindingContext для содержимого:
this.BindingContext = new MyContext();
код выше можно поместить в конструктор для
MainPage
Напоследок, демонстрация работоспособности
Я намеренно не приводил код для классов
ViewModelBase и
RelayCommand - это стандартный классы реализации которых вы легко сможете найти.
Например,
тут и
тут.