Отвечая на сам вопрос:
Обращение к чекнутым чекбоксам
Вы и вправду перепутали событие Checked со свойством IsChecked.
Давайте теперь поговорим о ваших ошибках в целом.
- Вы путаете WinForms и WPF, это совершенно разные платформы. В WPF принято использовать Binding, его помощником и как по мне неотъемлемая часть - MVVM.
- По правилам MVVM вы должны разделить свое приложение четко на 3 слоя (Model - View - ViewModel). И самое важное, о View слое (то есть наше окно, xaml дизайн) другие слои совершенно не должны знать! То есть делать
cbx1.Background = Red - не правильно!
Давайте по порядку разберем все, что у вас есть в Xaml:
<Grid Margin="0,0,2,-1">
<CheckBox x:Name="cbx1" Style="{DynamicResource MyCheckBox}" Content="1" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="62,59,0,0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<CheckBox x:Name="cbx4" Style="{DynamicResource ResourceKey=MyCheckBox}" Content="4" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="62,104,0,0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<CheckBox x:Name="cbx7" Style="{DynamicResource ResourceKey=MyCheckBox}" Content="7" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="62,150,0,0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<CheckBox x:Name="cbx2" Style="{DynamicResource ResourceKey=MyCheckBox}" Content="2" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="107,59,0,0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<CheckBox x:Name="cbx5" Style="{DynamicResource ResourceKey=MyCheckBox}" Content="5" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="107,104,0,0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<CheckBox x:Name="cbx8" Style="{DynamicResource ResourceKey=MyCheckBox}" Content="8" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="107,150,0,0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<CheckBox x:Name="cbx3" Style="{DynamicResource ResourceKey=MyCheckBox}" Content="3" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="152,60,0,0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<CheckBox x:Name="cbx6" Style="{DynamicResource ResourceKey=MyCheckBox}" Content="6" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="152,105,0,0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<CheckBox x:Name="cbx9" Style="{DynamicResource ResourceKey=MyCheckBox}" Content="9" HorizontalAlignment="Left" Height="40" VerticalAlignment="Top" Width="40" Margin="152,150,0,0" VerticalContentAlignment="Center" HorizontalContentAlignment="Center"/>
<Button Style="{DynamicResource btn}" Content="Выбрать" HorizontalAlignment="Left" Height="30" Margin="82,205,0,0" VerticalAlignment="Top" Width="89" Click="Button_Click"/>
</Grid>
Что вы здесь видите?
- Имена. Помним, да, код не должен знать о элементах. Получается они нам бесполезны.
- Стили. У вас на все элементы привязывается один стиль, почему бы не сделать его стандартным стилем (для этого достаточно у самого стиля убрать его Key (
x:Key="MyCheckBox").
- Margin. Я понимаю, вы хотели сделать сетку и все дела, но в WPF такой подход не верный! Вы не должны задавать Margin выше 10-20! Делайте сетку в самом Grid и помещайте в нужную ячейку то, что нужно.
- Остается всякая шелуха, которая везде одна и та жа (размеры, положение и др.), почему бы ее не занести в стиль?
Переписанный код в итоге будет примерно такой:
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Center" Margin="10">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0" Grid.Row="0" Content="1" />
<CheckBox Grid.Column="1" Grid.Row="0" Content="2" />
<CheckBox Grid.Column="2" Grid.Row="0" Content="3" />
<CheckBox Grid.Column="0" Grid.Row="1" Content="4" />
<CheckBox Grid.Column="1" Grid.Row="1" Content="5" />
<CheckBox Grid.Column="2" Grid.Row="1" Content="6" />
<CheckBox Grid.Column="0" Grid.Row="2" Content="7" />
<CheckBox Grid.Column="1" Grid.Row="2" Content="8" />
<CheckBox Grid.Column="2" Grid.Row="2" Content="9" />
</Grid>
<Button Style="{DynamicResource btn}" Content="Выбрать" Height="30" Width="89" Margin="0,5"/>
</StackPanel>
Ну и стиль.
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="#FF000000"/>
<Setter Property="Background" Value="Azure"/>
<Setter Property="Margin" Value="2"/>
<Setter Property="Width" Value="40"/>
<Setter Property="Height" Value="40"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Border x:Name="checkbx" BorderBrush="#FFC6C6C7" BorderThickness="1.5" CornerRadius="3" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="true">
<Setter Property="Background" Value="LightPink"/>
<Setter TargetName="checkbx" Property="CornerRadius" Value="30"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Хорошо, с ошибками покончили, давайте теперь посмотрим как можно это все удобно и правильно сделать!
Мы видим, что у нас идет определенное кол-во CheclBox'ов, расположены в сетке и нам надо удобно с ними работать. У нас есть два варианта 1. Это создать кучу свойств под каждый CheckBox и к ним привязываться. 2. Сделать динамическое заполнение этих кнопок и хранить их все в определенной коллекции. Как по мне, второй способ удобней, давайте реализуем:
Xaml
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Center" Margin="10" d:DataContext="{d:DesignInstance {x:Type local:MainViewModel}}">
<ItemsControl ItemsSource="{Binding CheckBoxes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" Rows="3" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Style="{DynamicResource btn}" Content="Выбрать" Command="{Binding SelectCommand}" Height="30" Width="89" Margin="0,5"/>
</StackPanel>
Тут мы создаем StackPanel, которая будет расположена в верху и по горизонтали в центре, отступ со всех углов 10. Ну и для удобства, что бы в дизайнере видеть все свойства, задаем через d:DataContext тип.
В StackPanel задаем ItemsControl, который будет размещать CheckBox'ы в сетке UniformGrid, которая размечает нашу область на равные 3х3 клетки. Самому ItemsControl задаем ItemsSource - это привязка к коллекции, в которой мы содержим все необходимые свойства для наших CheckBox'ов. У самих же CheckBox'ов мы делаем привязку к нужным свойствам (Content и IsChecked). А, ну и не забываем про саму кнопку, которая также размещена внутри StackPanel, у нее все по стандарту, единственное - Click поменяли на команду (опять же, MVVM и в нем желательно использовать команды).
Также нам нужно немного подправить стиль, а точнее его триггеры, ведь мы помним, что в MVVM делится все на слои и не есть хорошо в коде задавать цвета, ведь это задача View слоя. Перепишем триггеры следующим образом:
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding AlreadyUsed}" Value="True">
<Setter Property="Background" Value="Gray"/>
<Setter TargetName="checkbx" Property="CornerRadius" Value="30"/>
</DataTrigger>
<Trigger Property="IsChecked" Value="true">
<Setter Property="Background" Value="LightPink"/>
<Setter TargetName="checkbx" Property="CornerRadius" Value="30"/>
</Trigger>
</ControlTemplate.Triggers>
Тут добавляется DataTrigger, который проверяет наше свойство AlreadyUsed, если оно true, то задаем нужный цвет и др. свойства.
Код
В коде сразу первым делом делаем основных два класса:
INotifyPropertyChanged - Этот класс поможет нам узнать об изменениях наших свойств.
public class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
RelayCommand - Этот класс поможет в создание команд.
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute == null || canExecute(parameter);
}
public void Execute(object parameter)
{
execute(parameter);
}
}
Эти классы можно положить где нибудь в дальнем углу проекта и забыть, изменений в них делать вряд ли нужно будет. Ну чтож, создадим модель самих CheckBox'ов. В ней нам надо создать свойства для привязки и к тем, что могут измениться надо реализовать INPC:
public class CheckBoxModel : VM
{
public string Name { get; set; }
private bool isChecked;
public bool IsChecked
{
get => isChecked;
set
{
isChecked = !AlreadyUsed && value;
OnPropertyChanged();
}
}
private bool alreadyUsed;
public bool AlreadyUsed
{
get => alreadyUsed;
set
{
alreadyUsed = value;
OnPropertyChanged();
}
}
}
У свойства IsChecked можно увидеть некую проверку, которая не дает выделить объект, если его AlreadyUsed = true.
Ну что, считай последний шаг! Основная ViewModel! В ней нам нужно сделать коллекцию, заполнить ее и создать команду для кнопки:
public class MainViewModel
{
public ObservableCollection<CheckBoxModel> CheckBoxes { get; set; } = new ObservableCollection<CheckBoxModel>();
public RelayCommand SelectCommand { get; set; }
public MainViewModel()
{
for (var i = 1; i < 10; i++)
{
CheckBoxes.Add(new CheckBoxModel{Name = i.ToString()});
}
SelectCommand = new RelayCommand(_ => Select());
}
private void Select()
{
var items = CheckBoxes.Where(x => x.IsChecked);
foreach (var item in items)
{
item.AlreadyUsed = true;
item.IsChecked = false;
}
}
}
- CheckBoxes - наша основная коллекция, она ObservableCollection потому, что данная коллекция уже имеет необходимое для оповещения интерфейса при изменениях внутри коллекции.
- SelectCommand - Команда нашей кнопки.
- Конструктор - Через него я для теста с помощью цикла добавляю 10 элементов, i передаю в название. Также инициализирую команду, привязываю ее к методу
Select().
- Метод Select - Данный метод будет вызываться при нажатие на кнопку, в нем наша задача состоит в том, что бы взять все выделенные элементы и задать им AlreadyUsed. Ну и убрать само выделение.
Все, теперь смело можем привязать наше окно к MainViewModel и наслаждаться результатом! Пишем в MainWindow что то на подобие этого:
private MainViewModel MainViewModel { get; } = new MainViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = MainViewModel;
}
Результат:

показа данных о том сколько кнопок было выбрано
Тут ооочень простое решение. У нас уже все необходимое есть, главным источником данных является MainViewModel. Так давайте создадим в ней необходимое свойство. Так, как свойство динамичное (обновляется время от времени), то нам надо еще INPC:
- Наследуем VM класс
public class MainViewModel : VM.
Создаем свойство со счетчиком:
private int boxCount;
public int BoxCount
{
get => boxCount;
set
{
boxCount = value;
OnPropertyChanged();
}
}
Меняем ObservableCollection на BindingList (она оповещает совершенно обо всех изменениях в коллекции):
public BindingList<CheckBoxModel> CheckBoxes { get; set; } = new BindingList<CheckBoxModel>();
В конструкторе MainViewModel подписываемся на событие обновления коллекции:
CheckBoxes.ListChanged += CheckBoxesOnListChanged;
Ну и делаем, что нам нужно при изменениях:
private void CheckBoxesOnListChanged(object sender, ListChangedEventArgs e)
{
BoxCount = CheckBoxes.Count(x => x.IsChecked);
}
Остается у определенного элемента привязать его текст к нашему новому свойству, я поменяю content у кнопки. Content="{Binding BoxCount}".
