You must create a structure of a list of questions, where each question has a list of possible answers.
The following example uses the RelayCommand implementation provided by Microsoft Docs: Relaying Command Logic.
It uses a composed data model consisting of Question and Answer:
Question.cs
public class Question : INotifyPropertyChanged
{
public Question(string summary, Answer answer)
{
this.Summary = summary;
this.Answer = answer;
}
public string Summary { get; set; }
public Answer Answer { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Answer.cs
public class Answer : INotifyPropertyChanged
{
public Answer(IEnumerable<IChoice> choices)
{
this.Choices = choices;
}
// Evaluates the answer(s)
public bool Validate()
{
this.IsCorrect = this.Choices.All(
choice => choice.IsValidChoice && choice.IsSelected
|| !choice.IsValidChoice && !choice.IsSelected);
return this.IsCorrect;
}
public ICommand CheckAnswerCommand =>
new RelayCommand(answer => Validate());
public IEnumerable<IChoice> Choices { get; set; }
private bool isCorrect;
public bool IsCorrect
{
get => this.isCorrect;
private set
{
this.isCorrect = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
IChoice
public interface IChoice : INotifyPropertyChanged
{
// Marks whether the choice is a valid answer
bool IsValidChoice { get; }
// Selects the choice as an answer
bool IsSelected { get; set; }
string Text { get; set; }
}
MultiChoice.cs
class MultiChoice : IChoice
{
public MultiChoice(string text, bool isValidChoice)
{
this.Text = text;
this.IsValidChoice = isValidChoice;
}
#region Implementation of IChoice
public bool IsValidChoice { get; }
private bool isSelected;
public bool IsSelected
{
get => this.isSelected;
set
{
this.isSelected = value;
OnPropertyChanged();
}
}
private string text;
public string Text
{
get => this.text;
set
{
this.text = value;
OnPropertyChanged();
}
}
#endregion
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
SingleChoice.cs
class SingleChoice : IChoice
{
public SingleChoice(string text, bool isValidChoice)
{
this.Text = text;
this.IsValidChoice = isValidChoice;
}
#region Implementation of IChoice
public bool IsValidChoice { get; }
private bool isSelected;
public bool IsSelected
{
get => this.isSelected;
set
{
this.isSelected = value;
OnPropertyChanged();
}
}
private string text;
public string Text
{
get => this.text;
set
{
this.text = value;
OnPropertyChanged();
}
}
#endregion
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<Question> Questions { get; set; }
public ViewModel()
{
this.Questions = new ObservableCollection<Question>
{
new Question(
"Which number follows '1'?",
new Answer(
new[]
{
new SingleChoice("3", false),
new SingleChoice("15", false),
new SingleChoice("2", true),
new SingleChoice("7", false)
})),
new Question(
"Which creature can fly?",
new Answer(
new[]
{
new MultiChoice("Bird", true),
new MultiChoice("Elephant", false),
new MultiChoice("Bee", true),
new MultiChoice("Cat", false)
}))
};
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml
<Window xmlns:system="clr-namespace:System;assembly=mscorlib">
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<StackPanel>
<CheckBox Content="Answer 1 is Correct?" IsChecked="{Binding Questions[0].Answer.IsCorrect, Mode=OneWay}" />
<CheckBox Content="Answer 2 is Correct?" IsChecked="{Binding Questions[1].Answer.IsCorrect, Mode=OneWay}" />
<ListBox ItemsSource="{Binding Questions}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type viewModels:Question}">
<StackPanel>
<TextBlock Text="{Binding Summary}" />
<ListBox ItemsSource="{Binding Answer.Choices}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type viewModels:SingleChoice}">
<RadioButton Content="{Binding Text}"
GroupName="Answer"
IsChecked="{Binding IsSelected}"
Command="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext.Answer.CheckAnswerCommand}" />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:MultiChoice}">
<CheckBox Content="{Binding Text}"
IsChecked="{Binding IsSelected}"
Command="{Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext.Answer.CheckAnswerCommand}" />
</DataTemplate>
</ListBox.Resources>
</ListBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Window>
Set { _LoadRadioContent = value; RaisePropertyChange(); }in the setter ofLoadRadioContent. To get RaisePropertyChange(), please implement the INotifyPropertyChanged's methods.ListBoxand the initialization of theListBox?DataTemplateadd another nestedItemsControlto replace the currentRadioButtonwhereItemsControl.ItemsSourcebinds to the collection of possible answers. Then define aDataTemplatefor the newItemsControl.ItemTemplate. ThisDataTemplatecontains aRadioButtonwhereRadioButton.Contentbinds to the currentDataContext(the possible answer).