WPF databinding from DataTemplate - many-to-many relationship - wpf

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

Related

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.

How to make custom control with ListBox?

I want to make horizontal ListBox with customized item template, so I make a basic template of it.
However, I couldn't find an example of binding 'things' to that WPF XAML, especially with ListBox filled with customized items.
I simply want to dynamically add/remove items in the ListBox with Image, Label, ComboBox with previously filled with number 1 to 10.
the ADD/REMOVE button will be placed outside WPF control, it means that the buttons will be on the Main Window Form.
Also, there are TextBox and picture selector in the Main Window Form so that I can change the text and image.
Below is code behind XAML :
Public Class listSequence
Public Sub New()
InitializeComponent()
listbox.Items.Add("hi")
listbox.Items.Add("there")
End Sub
End Class
Below is XAML :
<ListBox Name="listbox" VerticalContentAlignment="Stretch" ScrollViewer.VerticalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True" >
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Stretch" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Border Padding="10" Margin="5" BorderThickness="1" BorderBrush="Aqua" CornerRadius="0" Width="120" VerticalAlignment="Stretch">
<StackPanel>
<Image />
<Label Content="{Binding}" />
<TextBlock Text="hi" />
<ComboBox x:Name="cboRepeat" ItemsSource="{Binding}" DisplayMemberPath="RepeatCounter" IsSynchronizedWithCurrentItem="True"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Let's say we have a basic class named Item:
public class Item : INotifyPropertyChanged
{
public string Text { get; set; } // Implement INotifyPropertyChanged
public string ImagePath { get; set; } // properly on these properties
}
And a collection of these in a view model:
public ObservableCollection<Item> Items { get; set; }
Now to display these items in the UI, we use a ListBox and set the ItemsSource property:
<ListBox ItemsSource="{Binding Items}">
</ListBox>
When it comes to defining the ListBox.ItemTemplate, you need to understand that this DataTemplate will be applied to each item and that it has access to all of the properties defined in the Item class:
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image ImageSource="{Binding ImagePath}" />
<TextBlock Text="{Binding Text}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Therefore, you can access the properties in the collection class as shown above. You can find out the full story by looking at the ItemsControl.ItemTemplate Property page on MSDN.

Switch between different views, following MVVM pattern

I'm looking for the correct way to switch between different views. My scenario is similar to the Widnows Explorer on Windows 8, where you can switch between 'extra large icons', 'small icons', 'details', etc. In Windows 8 The users do that from the 'View' ribbon of explorer (you can also change view in XP and 7).
In my case, I've a list of friends, and I want to let the user switch between 'small', 'large', and 'details' view.
Assume my view model has a list of friends:
public class FriendVM {
public name { get; set; }
public smallImage { get; set; }
public largeImage { get; set; }
};
public class MainVM : INotifyPropertyChanged {
public ObservableCollection<FriendVM> friends { get; set; }
private string m_viewMode
public string viewMode {
get { return m_viewMode; }
set { m_viewMode=value; this.PropertyChanged( new PropertyChangedEventArags("viewMode") ); }
}
}
In my view, I've a ribbon (on which user can change viewMode), a header (showing details about the user), and a list of friends. Depends on the view, I want to show:
when viewMode = "details" I've a ListView, with a GridView.
when viewMode = "small" I've a ListView, with ItemsPanel is a WrapPanel, and I bind the image to the smallImage
when viewMode = "large" I've a ListView with WrapPanel, using the largeImage property.
Here is how my XML looks like (simplified):
<Window x:Class="friends.MainWindow" ... xmlns:f="clr-namespace:friends" ...>
<Window.DataContext>
<f:MainVM />
<Window.DataContext>
<Window.Resources>
<ControlTemplate x:Key="details">
<ListView ItemsSource="{Binding Path=friends}">
<ListView.View>
<GridView>
...
</GridView>
</ListView.View>
</ControlTemplate>
<ControlTemplate x:Key="small">
<ListView ItemsSource="{Binding Path=friends}">
<ListView.ItemsPanel><WrapPanel Orientation="Horizontal" />
</ListView>
<ListView.ItemTemplate><DataTemplate>
<Image Source={Binding smallPicture} Width="32" Height="32" />
</DataTemplate></ListView.ItemTemplate>
</ControlTemplate>
<ControlTemplate x:Key="large">
<ListView ItemsSource="{Binding Path=friends}">
<ListView.ItemsPanel><WrapPanel Orientation="Horizontal" />
</ListView>
<ListView.ItemTemplate><DataTemplate>
<StackPanel>
<Image Source="{Binding largePicture}" Width="200" Height="200" />
<TextBlock Text="{Binding name}" />
</StackPanel>
</DataTemplate></ListView.ItemTemplate>
</ControlTemplate>
</Window.Resource>
<DockPanel LastChildFill="True">
<Ribbon ...>
...
</Ribbon>
<StackPanel>
... some header stuff
</StackPanel>
<ContentControl x:Name="friendList" Content="{Binding friends}" ?????? />
</DockPanel>
</Window>
So, my question is what do I do in the ????? area. Right now, I've Template="{StaticResource small}" and it is working. Moreover, using code behind I can change the Template to any of the other resourced template (using FindResource). However, I'm not happy with this solution, as I feel it doesn't go well with MVVM pattern. If it was an "Item" (a listbox item, a tab item, etc.), then I could use a data template, and then a date template selector. But since this is a ContentControl, and ControlTemplateSelector seems to be completely broken from the design, I'm not sure what should I do.
Or, if I could put the list of friends "as is" in the tree, then maybe using data template (having TargetType=f:FriendList) I could make it work, but I don't want to instantiate another friend list. There is already one instance instantiated within the DataContext element.

Conditional DataTemplates when binding to a collection

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>

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