How binding quiz with different type questions in WPF app using MVVM?
QuizPageViewModel:
public class QuizPageViewModel : ViewModelBase
{
public QuizPageViewModel()
{
QuizCollection = new ObservableCollection<QuizQuestion>();
}
public ObservableCollection<QuizQuestion> QuizCollection { get; set; }}
Where QuizQuestion: - EF Entity
public partial class QuizQuestion
{
public QuizQuestion()
{
QuizAnswers = new HashSet<QuizAnswer>();
QuizMultiQuestions = new HashSet<QuizMultiQuestion>();
}
public long Id { get; set; }
public int QuizId { get; set; }
***public String Type { get; set; }***
[Required]
public string Name { get; set; }
}
Question Type can be truefalse, multianswer, multichoice and other type
in xaml:
<ItemsControl ItemsSource="{Binding QuizCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Path=QuizAnswers}"
VerticalAlignment="Stretch"
Background="Transparent">
<ListBox.ItemTemplate>
<ItemContainerTemplate>
<RadioButton GroupName="{Binding Id}"
Content="{Binding Name}" />
</ItemContainerTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
this xaml code display only radiobutton question of quiz. How binding and display other Type of question (checkbox, combobox, textbox)? HELP)))
You can use an Item template selector in your items control to display different controls/templates depending on the question.
In the code below you can see that there's a conditional statement that checks the type of question and return a template based on the type of question.
public class QuiztemplateSelector : DataTemplateSelector
{
public DataTemplate TrueOrFalseTemplate { get; set; }
public DataTemplate MultiAnswerTemplate { get; set; }
public DataTemplate MultiChoiceTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var question = item as QuizQuestion;
if (question.Type.Equals("TruOrFalse"))
return TrueOrFalseTemplate;
else if (question.Type.Equals("MultiAnswer"))
return MultiAnswerTemplate;
else if ("MultiChoice")
return MultiChoiceTemplate;
return null; //Or your default Template.
}
}
Now in your xaml, you can create templates for the types of questions (i.e. true/False, Multi choice, multi answer). Then you need to pass those templates to the QuiztemplateSelector as shown below.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<DataTemplate x:Key="TrueOrFalse">
<!-- Write your True or false template here-->
</DataTemplate>
<DataTemplate x:Key="MultiChoice">
<!-- Write your MultiChoice template here-->
</DataTemplate>
<DataTemplate x:Key="MultiAnswer">
<!-- Write your multianswer Template here -->
</DataTemplate>
<local:QuizTemplateSelector x:Key="QuizTemplateSelector"
MultiAnswerTemplate="{StaticResource MultiAnswer}"
TrueOrFalseTemplate="{StaticResource TrueOrFalse}"
MultiChoiceTemplate="{StaticResource MultiChoice}" />
</Grid.Resources>
<ItemsControl ItemsSource="{Binding QuizCollection}" ItemTemplateSelector="{StaticResource QuiztemplateSelector}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Path=QuizAnswers}"
VerticalAlignment="Stretch"
Background="Transparent">
<ListBox.ItemTemplate>
<ItemContainerTemplate>
<RadioButton GroupName="{Binding Id}"
Content="{Binding Name}" />
</ItemContainerTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
You can read more abour DataTemplateSelector here.
Related
I have a Listbox which is bound to a DataTemplate that has another Listbox on it.
On DataTemplate there is a button that I want to use for adding items to DataTemplate ListBox, but I can't find a solution to do this.
Here is my listbox:
<Button Width="200" Content="Add Question" x:Name="btnAddQuestion" Click="btnAddQuestion_Click"/>
<StackPanel Orientation="Horizontal">
<ListBox Margin="5" x:Name="lvQuestions" ItemTemplate="{StaticResource TemplateQuestionTitle}">
</ListBox>
</StackPanel>
And this is DataTemplate:
<DataTemplate x:Key="TemplateQuestionTitle">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox materialDesign:HintAssist.Hint="Enter question" MinWidth="200" Style="{StaticResource MaterialDesignFloatingHintTextBox}"/>
<Button Content="+" Command="{Binding Source={x:Reference ThisPage},Path=DataContext.Command}" />
</StackPanel>
<ListBox ItemsSource="{Binding MyItems}" MinHeight="50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox>
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
This is code behind on my page:
public partial class UIBuilder:Window
{
private CommandVm _commandVm;
public UIBuilder()
{
InitializeComponent();
_commandVm = new CommandVm();
DataContext = _commandVm;
}
private void btnAddQuestion_Click(object sender, RoutedEventArgs e)
{
lvQuestions.Items.Add(null);
}
}
I have implemented this code on my ViewModel in order to add items to datatemplate ListBox:
public class CommandVm
{
public ObservableCollection<TextBox> MyItems { get; set; }
public CommandVm()
{
MyItems = new ObservableCollection<TextBox>();
Command = new RelayCommand<TextBox>(Execute);
}
private void Execute(TextBox textBox)
{
MyItems .Add(textBox);
}
public ICommand Command { get; set; }
}
I use to catch the Execute() function on button "+" click command, but my code doesn't add any ListBox item.
MyItems is a property of the parent view model which means that you should bind to it like this:
<ListBox ItemsSource="{Binding DataContext.MyItems,
RelativeSource={RelativeSource AncestorType=Window}}" MinHeight="50">
This also means that you are using one single collection of items for all questions. Besides this obvious design flaw, a view model should not contain any TextBox elements. This basically breaks what the MVVM pattern is all about.
What you should do to make this example MVVM compliant is to create a Question class that has a collection of items, e.g.:
public class Question
{
public Question()
{
AddAnswerCommand = new RelayCommand<object>(Execute);
}
private void Execute(object obj)
{
Items.Add(new Answer());
}
public ObservableCollection<Answer> Items { get; }
= new ObservableCollection<Answer>();
public ICommand AddAnswerCommand { get; }
}
public class Answer { }
The window's view model should then have a collection of questions:
public class CommandVm
{
public CommandVm()
{
AddQuestionCommand = new RelayCommand<object>(Execute);
}
public ObservableCollection<Question> Questions { get; }
= new ObservableCollection<Question>();
public ICommand AddQuestionCommand { get; }
private void Execute(object obj)
{
Questions.Add(new Question());
}
}
The view and the bindings could then be defined like this:
<Window.Resources>
<DataTemplate x:Key="TemplateQuestionTitle">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox MinWidth="200" />
<Button Content="+" Command="{Binding AddAnswerCommand}" />
</StackPanel>
<ListBox ItemsSource="{Binding Items}" MinHeight="50">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Width="200" Content="Add Question" Command="{Binding AddQuestionCommand}"/>
<ListBox Margin="5"
ItemsSource="{Binding Questions}"
ItemTemplate="{StaticResource TemplateQuestionTitle}" />
</StackPanel>
This setup lets you add individual elements to each separate question.
I have the following ListView with a DataTemplate that creates three TextBlocks and populates each entry with the data from class Item.
I want to set the width of each TextBlock to some value that is passed through with the ICollection<Item> array as a value that is the same for each entry. With the syntax below, each Item would have to have the values of GlobalWidth1, etc to be set for each instance.
Is there any way to pass the width values as a global values for the entire ICollection<Item> collection in WPF?
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Data1}" Width="{Binding GlobalWidth1}" />
<TextBlock Text="{Binding Data2}" Width="{Binding GlobalWidth2}" />
<TextBlock Text="{Binding Data3}" Width="{Binding GlobalWidth3}" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class Item
{
public string Data1 { get; set; }
public string Data2 { get; set; }
public string Data3 { get; set; }
}
Instead of using bindings you could define resource values within the XAML file itself and use {StaticResource ...}.
xmlns:system="clr-namespace:System;assembly=System.Runtime"
...
<ListView>
<ListView.Resources>
<system:Double
x:Key="GlobalWidth1">
100
</system:Double>
<system:Double
x:Key="GlobalWidth2">
120
</system:Double>
<system:Double
x:Key="GlobalWidth3">
150
</system:Double>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Data1}" Width="{StaticResource GlobalWidth1}" />
<TextBlock Text="{Binding Data2}" Width="{StaticResource GlobalWidth2}" />
<TextBlock Text="{Binding Data3}" Width="{StaticResource GlobalWidth3}" />
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You could even have these defined in your top level App.Resources and set them from the App.xaml.cs.
Note: If you add these to the App.Resources you will need to remove them from the local ListView.Resources
public App()
{
//hardcode
this.Resources.Add("GlobalWidth1", 100);
this.Resources.Add("GlobalWidth2", 120);
this.Resources.Add("GlobalWidth3", 150);
//or perhaps define them in the global settings
this.Resources.Add("GlobalWidth1", Settings.Default.GlobalWidth1);
this.Resources.Add("GlobalWidth2", Settings.Default.GlobalWidth2);
this.Resources.Add("GlobalWidth3", Settings.Default.GlobalWidth3);
}
Of course if you want to keep it using bindings you can just add static properties on the Item class:
public class Item
{
public string Data1 { get; set; }
public string Data2 { get; set; }
public string Data3 { get; set; }
public static double GlobalWidth1 => 100;
public static double GlobalWidth2 => 120;
public static double GlobalWidth3 => 150;
}
I would personally recommend keeping it in the XAML for I find it more organized to keep pure UI code in the View layer and out of the ViewModel layer (If you are sticking to MVVM)
This question already has answers here:
Update Listview with Object Data
(1 answer)
Static binding doesn't update when resource changes
(2 answers)
Closed 6 years ago.
So I've been looking for the wright solution for my problem. It seems I'm not able to bind to a observableCollection that is located in my models folder. I am able to add items in it and in a later stadium retrieve these with some C# Code. BUT I just can't seem to bind to the items from a page I named CreatePage.
I keep getting this message: Invalid binding path 'Questionlist' : Property 'Questionlist' can't be found on type 'CreatePage'.
Class with the observableCollection:
public partial class MyStatic
{
static MyStatic()
{
Questionlist = new ObservableCollection<QuestionList>();
}
public static ObservableCollection<QuestionList> Questionlist;
}
public class QuestionList
{
public string Question { get; set; }
public string Answer { get; set; }
public string Delete { get; set; }
public string Correct { get; set; }
public string Wrong { get; set; }
public string UserAnswer { get; set; }
}
This is my Xaml:
<ScrollViewer Grid.Row="4"
Grid.ColumnSpan="2"
Margin="20,10"
MaxWidth="650">
<RelativePanel>
<ListView Name="Questionviewlist"
ItemsSource="{x:Bind Questionlist}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:QuestionList">
<TextBlock Name="Questionview"
Text="{x:Bind Question}"
TextWrapping="Wrap"
FontFamily="Arial Rounded MT Bold"
FontWeight="Bold"
Foreground="Brown"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Name="Answerviewlist"
RelativePanel.AlignHorizontalCenterWithPanel="True"
ItemsSource="{x:Bind Questionlist}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:QuestionList">
<TextBlock Name="Answerview"
Text="{x:Bind Answer}"
FontFamily="Arial Rounded MT Bold"
FontWeight="Bold"
Foreground="Brown"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Name="Deleteviewlist"
RelativePanel.AlignRightWithPanel="True"
ItemsSource="{x:Bind Questionlist}"
IsItemClickEnabled="True"
ItemClick="Deleteviewlist_ItemClick">
<ListView.ItemTemplate>
<DataTemplate x:DataType="data:QuestionList">
<TextBlock Name="Deleteview"
Text="{x:Bind Delete}"
TextWrapping="WrapWholeWords"
Foreground="#FFB57C"
FontStyle="Italic"
FontFamily="Arial Rounded MT Bold"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</RelativePanel>
</ScrollViewer>
In the above image:
You may note there is a little rectangle box around the tabHeader "title".
When I click inside the rectangle box, the tab does not get selected.
When I click outside the box, it does.
Code C#:
public class Lexicon : ObservableCollection<LexiconEntry>
{
public String leftLanguage { get; set; }
public String rightLanguage { get; set; }
public String name { get; set; }
public Lexicon(String name, String leftLanguage,String rightLanguage)
{
this.leftLanguage = leftLanguage;
this.rightLanguage = rightLanguage;
this.name = name;
}
}
public partial class MainWindow : System.Windows.Window
{
public List<Lexicon> lexicons;
public MainWindow()
{
InitializeComponent();
lexicons = new List<Lexicon>();
lexicons.Add(new Lexicon("foo_title","russian","french"));
lexicons.Add(new Lexicon("bar_title", "french", "english"));
lexicons.Add(new Lexicon("baz_title", "russian", "french"));
TheTabControl.ItemsSource = lexicons;
}
}
Xaml CODE:
<Window x:Class="InterpreterNotepad.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:avalonDock="http://schemas.xceed.com/wpf/xaml/avalondock"
Title="InterpreterNotepad" WindowStartupLocation="CenterScreen" WindowState="Maximized" x:Name="mainWindow">
...
<DockPanel>
<Menu DockPanel.Dock="Top">
...
</Menu>
<TabControl x:Name="TheTabControl">
<TabControl.ItemTemplate>
<DataTemplate>
<TabItem Header="{Binding name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<!-- Content -->
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="bqr"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
Got it, Sorry for this newbie question, I'm new to WPF. Using snoop, I saw that my rectangle box was actually a tabItem inside a tabItem!
I thought the dataTemplate described the template to be repeated in the tabControl, while it describes the content of each tabItem.
I Need to put:
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
Instead of:
<TabControl.ItemTemplate>
<DataTemplate>
<TabItem Header="{Binding name}"/>
</DataTemplate>
</TabControl.ItemTemplate>
I'm attempting to databind to a Windows Phone 8 Toolkit Expander view with the following XAML and C# class. I know that the DataContext is set properly because the Headers have the proper text. However, the rest of the items aren't set properly (except for the ExpanderTemplate)
<phone:PanoramaItem Header="Skill Sheet">
<ListBox Name="SkillSheet" ItemsSource="{Binding}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<toolkit:ExpanderView Header="{Binding}"
ItemsSource="{Binding}"
IsNonExpandable="False">
<toolkit:ExpanderView.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding groupName}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" LineHeight="{StaticResource LongListSelectorGroupHeaderFontSize}" />
</DataTemplate>
</toolkit:ExpanderView.HeaderTemplate>
<toolkit:ExpanderView.ExpanderTemplate>
<DataTemplate>
<TextBlock Text="Test" />
</DataTemplate>
</toolkit:ExpanderView.ExpanderTemplate>
<!--This is the area that is not getting databound-->
<toolkit:ExpanderView.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding skillNames}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding skill}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</toolkit:ExpanderView.ItemTemplate>
</toolkit:ExpanderView>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</phone:PanoramaItem>
And here are the classes that the XAML is getting bound to:
public class TreeMapSkill
{
public string skill { get; set; }
}
public class TreeMapping
{
public string groupName { get; set; }
public List<TreeMapSkill> skillNames { get; set; }
public TreeMapping()
{
skillNames = new List<TreeMapSkill>();
}
}
public class TreeMappingList
{
public List<TreeMapping> mapping { get; set; }
public TreeMappingList() { }
public TreeMappingList(Dictionary<string, List<string>> map)
: base()
{
this.mapping = new List<TreeMapping>();
foreach (string key in map.Keys)
{
TreeMapping tMap = new TreeMapping();
tMap.groupName = key;
foreach (string val in map[key])
tMap.skillNames.Add(new TreeMapSkill() { skill = val });
this.mapping.Add(tMap);
}
}
The Dictionary in the constructor is simply a list of skills associated to a specific group. I can also provide a sample object if it's needed for additional reference.
Why are you adding a ListBox inside the Expander's ItemTemplate? It is already a controls collection so you don't need a ListBox in there. Just put your DataTemplate inside.
<toolkit:ExpanderView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding skill}" />
</DataTemplate>
</toolkit:ExpanderView.ItemTemplate>
The second thing is you need to specify the property path on the binding of the ItemSource property for the expander.
<toolkit:ExpanderView Header="{Binding}"
ItemsSource="{Binding skillNames}"
IsNonExpandable="False">