How to use DataTemplates to support inheritance with ListBox - c#

I have a ListBox, its data source is an observable array of some objects. These objects all are derived from a base class.
I am trying to give each instance of the array a certain data template to handle its differences with other instances.
abstract class Base
{
public string a {get; set;};
}
class sub1 : Base
{
public string prop1 {get; set;};
}
class sub2 : Base
{
public string prop2 {get; set;};
}
If the array contains two instances, one is sub1, the other sub2, the list box should display for the first the two properties a and prop1, and for the second instance a and prop2.
Please advise,

You can create a DataTemplateSelector:
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate Sub1Template { get; set; }
public DataTemplate Sub2Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is sub1) { return Sub1Template; }
if (item is sub2) { return Sub2Template; }
return null;
}
}
Then use it as follows:
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="TemplateForSub1">
<StackPanel>
<TextBlock Text="{Binding a}" />
<TextBlock Text="{Binding prop1}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="TemplateForSub2">
<StackPanel>
<TextBlock Text="{Binding a}" />
<TextBlock Text="{Binding prop2}" />
</StackPanel>
</DataTemplate>
<my:MyTemplateSelector x:Key="MySelector"
Sub1Template="{StaticResource TemplateForSub1}"
Sub2Template="{StaticResource TemplateForSub2}" />
</UserControl.Resources>
<ListBox ItemsSource="{Binding SomeCollectionSomewhere}"
ItemTemplateSelector="{StaticResource MySelector}" />
</UserControl>
That should get you started.
Update: You can certainly use <DataTemplate DataType="{x:Type ...}" ...> to select a data template strictly based on the item type. It may be simpler to do so in a number of cases. A DataTemplateSelector can offer some flexibility that DataType= cannot, such as changing a template based on a value inside a class, or the results of a method call, etc. Choose whichever one works for you.

You need to specify a template for each of the types you want to display. Try something like this:
<ListBox ItemsSource="{Binding MyArray}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:sub1}">
<StackPanel>
<TextBlock Text="{Binding a}"/>
<TextBlock Text="{Binding prop1}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:sub2}">
<StackPanel>
<TextBlock Text="{Binding a}"/>
<TextBlock Text="{Binding prop2}"/>
</StackPanel>
</DataTemplate>
</ListBox.Resources>
</ListBox>
MyArray is the array containing your instances.
local is the namespace for your classes sub1 and sub2

Related

Passing a global value to DateTemplate in WPF

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)

How generate(binding) quiz in form using wpf mvvm

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.

Access a class instance inside a other class from xaml in wpf

My class looks like this:
public class testclass
{
public List<otherClass> references { get { return _references; } }
}
My otherClass looks like this
public class otherClass
{
public string name { get; set; }
}
And now i try to access this "otherClass" inside a DataTemplate
<DataTemplate x:Key="templateCore" DataType="{x:Type vm:AdminInterfaceViewModel}" >
<GroupBox DataContext="{Binding references }">
...
</DataTemplate>
this works fine, or i think at least, beaucse intellisense autocomplete it. But now how can i get access to the name property of the "otherClass" ?
All you need is to binding the List to a ItemsControl type,such as ListBox,DataGrid etc,and the ItemsControl will use the 'otherClass' instance in the List as the DataContext for each item in it.So you can find a 'mapping' there:
'List<otherClass>'--'ItemsControl'
'otherClass'--'Item'
.
I suppose that 'AdminInterfaceViewModel' is your DataContext,and 'references' is one property of it, so try this:
<DataTemplate x:Key="templateCore" DataType="{x:Type vm:AdminInterfaceViewModel}" >
<GroupBox>
<ListBox ItemsSource="{Binding references}">
<ListBox.ItemTemplate>
<DataTemplate>
<TexBox Text="{Binding name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
</DataTemplate>
> Update:
1.Suppose that you have a MainViewModel which contains a property named MyViewModel in type of 'AdminInterfaceViewModel '.
class MainViewModel
{
public AdminInterfaceViewModel MyViewModel {get; set;}
}
2.You have set the 'MainViewModel' as the DataContext of your Window,then you can use the property 'MyViewModel' in xaml.
<Window>
<Grid>
<ContentControl Margin="20" Content="{Binding MyViewModel}">
</ContentControl>
</Grid>
</Window>
3.Define the DataTemplate in your ResourceDictionary such as 'generic.xaml'.Remove the x:Key then the DataTemplate will automatically applied to every 'AdminInterfaceViewModel' type instance.
<DataTemplate x:Key="templateCore" DataType="{x:Type vm:AdminInterfaceViewModel}" >
<GroupBox>
<ListBox ItemsSource="{Binding references}">
<ListBox.ItemTemplate>
<DataTemplate>
<TexBox Text="{Binding name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
</DataTemplate>
> Tips:
Check this link,it may solve your potential problems:MVVM pattern

How to have recursion, or a hierarchy lists and sublists in XAML?

Say I have the following class, Employee
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public ObservableCollection<Employee> Underlings { get; set; }
}
And then I have the following XAML, bound to an ObservableCollection<Employee> MyEmployees
<ListBox ItemsSource="{Binding Path=MyEmployees}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Tag="{Binding Path=Employee.Id}">
<TextBlock Text="{Binding Path=Employee.Name}"></TextBlock>
<!-- Here's where I declare my underlings -->
<ListBox ItemsSource="{Binding Path=Employee.Underlings}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Tag="{Binding Path=Employee.Id}">
<TextBlock Text="{Binding Path=Employee.Name}"></TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This allows each employee in the collection MyEmployees to have some underlings. But those underlings are also of type employee, and could have their own underlings. How do I cater for those additional levels without making my XAML very complex?
Is there some way to declare my DataTemplate separately and allow it to be referenced within itself?
Do I have to do all this from code-behind instead?
(I realise the XAML above may not be 100% correct, its just an example)
therefore you have to use a TreeView and not a ListBox. And You have to specify a HierarchicalDataTemplate.
You could define the DataTemplate inside the ListBoxes Resources as the default template for Employees:
<ListBox ItemsSource="{Binding MyEmployees}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type myns:Employee}">
<Grid Tag="{Binding Id}">
<TextBlock Text="{Binding Name}"></TextBlock>
<ListBox ItemsSource="{Binding Underlings}" />
</Grid>
</DataTemplate>
</ListBox.Resources>
</ListBox>

How can I data bind a list of strings to a ListBox in WPF/WP7?

I am trying to bind a list of string values to a listbox so that their values are listed line by line. Right now I use this:
<ListBox Margin="20" ItemsSource="{Binding Path=PersonNames}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Id}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But I don't know what I am supposed to put into the textblock, instead of Id, since they are all string values, not custom classes.
Also it complains not having to find the PersonNames when I have it inside MainPage, as MainPage.PersonNames.
I set the data context to:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
I am doing it wrong?
If simply put that your ItemsSource is bound like this:
YourListBox.ItemsSource = new List<String> { "One", "Two", "Three" };
Your XAML should look like:
<ListBox Margin="20" Name="YourListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Update:
This is a solution when using a DataContext. Following code is the viewmodel you will be passing to the DataContext of the page and the setting of the DataContext:
public class MyViewModel
{
public List<String> Items
{
get { return new List<String> { "One", "Two", "Three" }; }
}
}
//This can be done in the Loaded event of the page:
DataContext = new MyViewModel();
Your XAML now looks like this:
<ListBox Margin="20" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The advantage of this approach is that you can put a lot more properties or complex objects in the MyViewModel class and extract them in the XAML. For example to pass a List of Person objects:
public class ViewModel
{
public List<Person> Items
{
get
{
return new List<Person>
{
new Person { Name = "P1", Age = 1 },
new Person { Name = "P2", Age = 2 }
};
}
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
And the XAML:
<ListBox Margin="20" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You should show us the code for PersonNames, and I am not sure I understand your question, but maybe you want to bind it like this:
<TextBlock Text="{Binding Path=.}"/>
or
<TextBlock Text="{Binding}"/>
This will bind to the current element in the list (assuming PersonNames is a list of strings). Otherwise, you will see the class name in the list.
If the items source is enumerable as string-entries, use the following:
<TextBlock Text="{Binding}"></TextBlock>
You can use this syntax on any object. Generally, the ToString() -method will then called to get the value. This is in many cases very handy. But beware that no change notification will occur.
You can do this without having to explicitly define the TextBlock control as a part of your ListBox (unless you want better formatting). The trick to getting the binding to trigger is using an ObservableCollection<string> instead of List<string>
Window1.xaml
<ListView Width="250" Height="50" ItemsSource="{Binding MyListViewBinding}"/>
Window1.xaml.cs
public Window1()
{
InitializeComponent();
DataContext = this;
// Need to initialize this, otherwise you get a null exception
MyListViewBinding = new ObservableCollection<string>();
}
public ObservableCollection<string> MyListViewBinding { get; set; }
// Add an item to the list
private void Button_Click_Add(object sender, RoutedEventArgs e)
{
// Custom control for entering a single string
SingleEntryDialog _Dlg = new SingleEntryDialog();
// OutputBox is a string property of the custom control
if ((bool)_Dlg.ShowDialog())
MyListViewBinding.Add(_Dlg.OutputBox.Trim());
}

Categories

Resources