Conditional DataTemplates when binding to a collection - wpf

Sample Application:
The sample application that the supplied code belongs to displays a list of Vehicle objects via Binding. The Vehicle class is a top level class that subclasses can derive from e.g. Car and Bike. The sample application at the moment displays the owner's name of the Vehicle.
Sample Model code:
public class Vehicle
{
private string _ownerName;
public string ownerName
{
get { return _ownerName; }
set { _ownerName = value; }
}
}
public class Car : Vehicle
{
public int doors;
}
public class Bike : Vehicle
{
// <insert variables unique to a bike, ( I could not think of any...)>
}
UserControl XAML Code:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="itemTemplate">
<WrapPanel>
<TextBlock Text="{Binding Path=ownerName}"/>
</WrapPanel>
</DataTemplate>
</Grid.Resources>
<ListBox x:Name="list" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5" ItemsSource="{Binding}" ItemTemplate="{StaticResource itemTemplate}" />
</Grid>
UserControl code behind:
public List<Vehicle> vehicleList = new List<Vehicle>();
public CustomControl()
{
InitializeComponent();
createSomeVehicles();
list.DataContext = vehicleList;
}
public void createSomeVehicles()
{
Car newcar = new Car();
newcar.doors = 5;
newcar.ownerName = "mike";
Bike newbike = new Bike();
newbike.ownerName = "dave";
vehicleList.Add(newcar);
vehicleList.Add(newbike);
}
What I want to be able to do:
I would like to be able to display a button in the list object dependant upon the Type of the Vehicle object. E.g. I would like to display a Open Boot button within the list item for Car's; Type Bike does not have a boot and so no button would display within the list item.
Idea's on how to accomplish this:
I have looked into the custom binding of different DataTemplates based upon what type of object it is. E.g. from the code behind I could call:
object.Template = (ControlTemplate)control.Resources["templateForCar"];
The problem here is that I am using a Binding on the whole list and so there is no way to manually bind a DataTemplate to each of the list items, the list binding controls the DataTemplate of it's items.

You can create a DataTemplate for each Bike and Car (and for any CLR type). By specifying the DataTemplate's DataType property, the template will automatically be applied whenever WPF sees that type.
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Car}">
<WrapPanel>
<TextBlock Text="{Binding Path=ownerName}"/>
<Button Content="Open Boot" ... />
</WrapPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Bike}">
<WrapPanel>
<TextBlock Text="{Binding Path=ownerName}"/>
</WrapPanel>
</DataTemplate>
</Grid.Resources>
<ListBox x:Name="list" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="5" ItemsSource="{Binding}" />
</Grid>

Related

C# WPF What's wrong with this template?

I'm trying to create a new window (child of MainWindow) that will display different numbers of rows with data each time. I used the following template that I copied from a wpf template tutorial. I've checked that the data are right and I can project them right with a MessageBox.Show window but I can't make them appear as bound properties. Here is the xaml...
<ListView x:Name="listUnits" x:FieldModifier="public" HorizontalAlignment="Left" Height="Auto" Margin="5,5,5,5" VerticalAlignment="Top" Width="Auto">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Id:"/>
<TextBlock Text="{Binding pu_Id}" Width="Auto"/>
<TextBlock Text=" "/>
<TextBlock Text="{Binding pu_unitName}" Width="Auto"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
So although pu_unitName contains the data, they're are displayed in the new window.
The binding is done in the constructor:
public partial class ChooseUnit : Window
{
public ChooseUnit(List<XML_Handler.PrUnits> thisList)
{
InitializeComponent();
listUnits.ItemsSource = thisList;
}
}
The PrUnits is a class which contains variables including pu_id and pu_unitName. So what's wrong with this code?
Since you can only bind to public properties, you need to make sure that pu_Id and pu_UnitName are properties (and not fields) of the PrUnits class. Then your bindings will work.
Also note that this is not a binding:
listUnits.ItemsSource = thisList;
In general, you would set the DataContext of the parent window to an instance of a view model class and bind the ItemsSource property of the ListView to a property that returns the List<XML_Handler.PrUnits>:
<ListView x:Name="listUnits" ItemsSource="{Binding Units}">
...
In your .cs:
public partial class ChooseUnit : Window
{
public ChooseUnit(List<XML_Handler.PrUnits> thisList)
{
InitializeComponent();
this.DataContext = thisList;
}
}
What this does it assigns the data context of your window to the list of itemsa you are trying to pass. At this point every element in that window will have access to the passed in list.
And then in your XAML do this:
<ListView x:Name="listUnits" ItemsSource="{Binding .}" x:FieldModifier="public" HorizontalAlignment="Left" Height="Auto" Margin="5,5,5,5" VerticalAlignment="Top" Width="Auto">
This allows us to use binding as the entire window has access to the list. Hence ItemsSource="{Binding .}".
EDIT
Reading more into your question and thanks to Ash your class needs to expose Properties with INotifyPropertyChanged interface implemented.
This interface is expected by the WPF framework so it can subscribe to the changed events and update the UI.

How to choose View dynamically based on current DataContext view model

I have a Page which will receive a different DataContext (View Model), dynamically.
I can't figure out how to use DataTemplate in a switch/case fashion, to render the appropriate view based on the current context.
I would imagine that I will have multiple DataTemplates like this:
<DataTemplate DataType="{x:Type LocalViewModels:ABC}">
<LocalViews:ABC/>
</DataTemplate>
but can't figure out in what container to put them. Only one of them will be rendered at a time, so ListBox makes no sense to me.
Given the following XAML of a Window
<Window.Resources>
<DataTemplate DataType="{x:Type local:ABC}">
<Border BorderThickness="2" BorderBrush="Red">
<TextBlock Text="{Binding Text}"/>
</Border>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding}"/>
</StackPanel>
you can simply assign an instance of ABC to the Window's DataContext to create the templated view.
class ABC
{
public string Text { get; set; }
}
...
public MainWindow()
{
InitializeComponent();
DataContext = new ABC { Text = "Hello, World." };
}
All details are here: Data Templating Overview.

WPF MVVM pattern

i am doing a simple project with mvvm pattern. its about one list that every row has one textbox and delete button and at
buttom we have one text box and add button like this:
name1 buttondelete
name2 buttondelete
name3 buttondelete
.
.
textbox buttonadd
with click the buttondelete the row should delete and with click bottonadd the text of textbox should insert in list as new
row.
i have three layer Sepand.WPFProject.Model , Sepand.WPFProject.ViewModel , Sepand.WPFProject.View;
in model i have context and repository and model (here my model is Category that have Name & ID property) class. repository is like this:
public class ModelRepository<T>
where T : class
{
ModelDbContext ctx = new ModelDbContext();
public IQueryable<T> GetAll()
{
IQueryable<T> query = ctx.Set<T>();
return query;
}
public void Add(T entity)
{
ctx.Set<T>().Add(entity);
ctx.SaveChanges();
}
public void Delete(T entity)
{
ctx.Set<T>().Remove(entity);
ctx.SaveChanges();
}
in viewModel i have categoryViewModel class like this:
public class CategoryViewModel
{
ModelRepository<Category> repository = new ModelRepository<Category>();
ObservableCollection<Category> categories = new ObservableCollection<Category>();
Category category = new Category();
public ObservableCollection<Category> GetAll()
{
IQueryable<Category> categoryRepository = repository.GetAll();
foreach (Category Category in categoryRepository)
categories.Add(Category);
return categories;
}
public ObservableCollection<Category> GetAllCategories
{
get { return GetAll(); }
}
public string TxtName
{
get { return category.Name; }
set { category.Name = value; }
}
in View in code behind i have
this.DataContext = new CategoryViewModel();
and in XAML i have
<Window.Resources>
<DataTemplate x:Key="CategoryTemplate">
<Border Width="400" Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4">
<StackPanel Grid.Row="0" Orientation="Horizontal">
<TextBlock Width="300" Margin="5" Text="{Binding Path=Name}"></TextBlock>
<Button Name="btnDeleteCategory" Width="50" Margin="5" Click="btnDeleteCategory_Click" >-</Button>
</StackPanel>
</Border>
</DataTemplate>
</Window.Resources>
.
.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Column="0" Grid.Row="0" Name="lstCategory" ItemTemplate="{StaticResource CategoryTemplate}" ItemsSource="{Binding Path=GetAllCategories}"/>
<StackPanel Margin="5" Grid.Column="0" Grid.Row="1" Orientation="Horizontal">
<Label Content="Name : "/>
<TextBox Name="TxtName" Text="{Binding Path=TxtName ,Mode=TwoWay}" Width="260"/>
<Label Width="50"/>
<Button Width="50" Content="+" Name="btnAddCategory" Click="AddCategory_Click" />
</StackPanel>
</Grid>
</Grid>
and now when i run app the listbox populated with data from database; but i could not write code for addbutton and
delete button;
could anyone tell me what should i do?
and why i could not bind the text of textbox in list to TxtName Property of CategoryViewModel class ?
i mean here
<TextBlock Width="300" Margin="5" Text="{Binding Path=Name}"></TextBlock>
when i write Binding Path=TxtName the list box would not show data but with Binding Path=Name
it shows data from database
Your question is a bit scattered. But I'll try address what I think are your issues.
You say in the code behind you have:
this.DataContext = new CategoryViewModel();
But nothing else.
First thing to do with checking why your button isn't working would be to see what action it is performing. Your XAML states it's using a click event:
btnDeleteCategory_Click
Where's that? Is it not in your code-behind too? It might be that you've not got anything and that's why your button isn't doing anything - you've not instructed it to do anything!
In MVVM you should be binding your button using Commands in your ViewModel, similarly to how you bind data to Properties in your ViewModel.
You need something like:
Command="{Binding Path=DeleteCommand}"
in your view, and:
public ICommand DeleteCommand
{
get { return new DelegateCommand<object>(FuncToCall, FuncToEvaluate); }
}
private void FuncToCall(object context)
{
//this is called when the button is clicked - Delete something
}
private bool FuncToEvaluate(object context)
{
//this is called to evaluate whether FuncToCall can be called
//for example you can return true or false based on some validation logic
return true;
}
Binding to TxtName might not be working because it does not implement/call PropertyChanged.

WPF databinding from DataTemplate - many-to-many relationship

I have a WPF control for laying out items and set ItemsSource for it as
ItemsSource="{Binding item_portfolio}" DataContext="{Binding SelectedItem}"
In the layout control's Resources I set the template for its items:
<Style>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="5">
<TextBlock Text="{Binding portfolio.PortfolioName}" />
<ListView ItemsSource="{Binding ?}">
</ListView>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The data for the bindings is a many-to-many relationship (one item can have many portfolios and one portfolio can have many items) and specified in 3 separate tables in the database (I use Entity Framework to access it). Schema and example data below:
item item_portfolio portfolio
ID (PK) Name itemID (FK) portfolioID (FK) ID PortfolioName
1 Item 1 1 1 1 Portfolio 1
2 Item 2 1 2 2 Portfolio 2
1 3 3 Portfolio 3
2 2
2 3
TextBlock binding under DataTemplate works correctly.
I don't however know how to bind the ListView ItemsSource so that it would show all the item objects belonging to that portfolio.
Edit:
I want to list portfolios in the layout control. Then under portfolio, I want to show what items it contains. The below image shows the UI when the SelectedItem is Item 1.
(First I show what portfolios this item has. This gives 3 portfolios. This works ok. Then on UI I click the portfolio 3, and it should show all items (Item 1 and Item 2) belonging to that portfolio. That doesn't work yet.)
We don't model our data models in code in the same way as they are modelled in the database. We don't make 'joining classes' to model 'joining tables'. Instead, we create hierarchical data models. In your case, you should have an Item class that has a Portfolio property of type Portfolio. There should be no ItemPortfolio class, as it makes no sense. Then, your Binding should be this`:
<ItemsControl ItemsSource="{Binding Items}" ... />
Then your DataTemplate (which will automatically have its DataContext set to an item from the Item class collection) should look more like this:
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="5">
<TextBlock Text="{Binding PortfolioName}" />
<ListView ItemsSource="{Binding Portfolio}">
</ListView>
</StackPanel>
</DataTemplate>
Clearly, to make this work, you'd need to have those properties defined in your Item class.
UPDATE >>>
In that case, make a Portfolio class with an Items property of type ObservableCollection<Item> and set the ItemsSource like this:
<ItemsControl ItemsSource="{Binding Item.Portfolios}" ... />
To clarify, your Item class should also have a collection property of type ObservableCollection<Portfolio>... this will lead to some data object duplication, but in WPF it is more important to provide your views with the data that they require.
I used Expander control here as you want PortfolioName as header and on click of
PortfolioName you want to display corresponding PortfolioItemList.
xaml code
<ItemsControl MaxHeight="300" ItemsSource="{Binding PortfolioInfo}" ScrollViewer.VerticalScrollBarVisibility="Auto" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"></StackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander>
<Expander.Header>
<TextBlock Background="YellowGreen" Text="{Binding name}"></TextBlock>
</Expander.Header>
<Expander.Content>
<ListBox ItemsSource="{Binding ItemList}"></ListBox>
</Expander.Content>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C# code
public partial class MainWindow : Window
{
public ObservableCollection<Portfolio> PortfolioInfo { get; set; }
public MainWindow()
{
InitializeComponent();
PortfolioInfo = new ObservableCollection<Portfolio>()
{
new Portfolio(){name="Portfolio1", ItemList= new List<string>(){"Item1","Item2","Item3"}},
new Portfolio(){name="Portfolio2", ItemList= new List<string>(){"Item1","Item2","Item3"}},
new Portfolio(){name="Portfolio3", ItemList= new List<string>(){"Item1","Item2","Item3"}}
};
this.DataContext = this;
}
}
public class Portfolio
{
public string name { get; set; }
public List<string> ItemList { get; set; }
}
Note : Edit Expander style/template to achieve desired UI
Rseult

Display Dynamic Number in WPF TabItem Header

I have a TabControl where every item contains a User Control called Timeline. This "Timeline" has a property called "Number" which changes during runtime.
I want to make the property "Number" to be displayed in the TabItem header. And i have really no idea how to do that to be honest.
My first thought is that i have to create a Custom Control that derives from the original TabItem Control and create a DependencyProperty or something with a custom ControlTemplate.
I feel that i'm pretty bad on explaining this...
An example: I Want to do something like the third image in the post on following url, but instead of the close-button, i want to display the property "Number" that dynamically changes during runtime!
http://geekswithblogs.net/kobush/archive/2007/04/08/closeabletabitem.aspx
If we have this class:
public class MyItem : INotifyPropertyChanged
{
public string Title {get; set;}
private int number;
public int Number
{
get { return number; }
set
{
number= value;
OnPropertyChanged("Number");
}
}
}
We can bind the collection of items to TabControl:
<TabControl ItemsSource="{Binding MyItems}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}"/>
<TextBlock Text="{Binding Number}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<my:TimeLine Number="{Binding Number, Mode=TwoWay}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

Resources