Data binding to a ViewModel property from a collections Binding? - wpf

I will try to keep this as succinct as possible.
I have a ViewModel with a collection of Models (i.e. Airplanes).
I have a Xaml page that is binded to the ViewModel.
The Xaml page has a DataGrid that is binded to the Airplanes collections.
For one of the DataGrid column templates, I want it to show a list of "EngineComponents", where EngineComponents is a collection of items defined in the ViewModel.
The catch is this:
The EngineComponents is a collection of parts that is essentially static. All Airplance rows in the DataGrid should show the same list of EngineComponents.
Airplanes
How to solve this EngineComponents binding question without writing extra code (event handlers, etc)?

You need to use a RelativeSource. I'll use a ListView in my example, but the ideas the same.
<UserControl ... DataContext="{Binding ...}">
<ListView ItemsSource="{Binding Airplanes}">
<ListView.View>
<GridView>
<GridViewColumn Header="Drawing No." Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.AvailableAirplanes,RelativeSource={RelativeSource AncestorType=UserControl}}"
SelectedItem="{Binding Airplane}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</UserControl>

Related

How to bind to template-generated elements in a ListView

I have a ListView where for each item I'd like to add a TextBox for user input, and then be able bind to its content per selected item (or using an indexer) from outside of the ListView.
To illustrate:
<ListView Name="lv" ItemsSource="{Binding Source={StaticResource lvSrc1}}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Loc">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Name="tbGv" Text="User input" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
<TextBlock Text="{Binding ElementName=lv, Path=SelectedItem[tbGv]}" />
Of course the above binding won't work, as SelectedItem returns an object from the underlying collection, which has nothing to do with the added TextBox.
How can I access the generated TextBoxes using pure XAML?
Edit:
I've now found a couple of posts with somewhat similar problems, where solutions presented are trickier than I expected and not based on XAML.
So rather, would it be possible to access ListViewItem (not the data its bound to) and its properties? This way I think I could pass text from my TextBox through the Tag property of ListViewItem.
I'm quite new to WPF so I assume that my problem and its phrasing is ridiculous, but please be gentle ;)

Which components of a ListView have to be explicitly provided when utilising Control Templates?

The solution to my question might just be specifically related to WPF ListViews, but it could also just be a control template issue in general. I don't know.
I have the following 3 property class, with overridden ToString method defined in my codebehind:
public class WorkListViewItem
{
public int Id { get; set; }
public string Namey { get; set; }
public bool d { get; set; }
public override string ToString()
{
return "ID: " + Id + "\r\nName: " + Namey + "\r\nd?:" + d.ToString();
}
}
I have a working ListView defined in my markup, as well as Data Templates defined under the window's resources for all 3 class properties, and an array of WorkListViewItem defined under an all-parent grid's resources.
(DTNamey also has two-way binding, a textbox, and a TextChanged event attached, but that makes no difference to my issue)
Data Templates:
<DataTemplate x:Key="DTNamey" DataType="{x:Type local:WorkListViewItem}">
<TextBox Text="{Binding Path=Namey, Mode=TwoWay}" Width="65" Foreground="BlueViolet" TextChanged="TextBox_TextChanged"/>
</DataTemplate>
<DataTemplate x:Key="DTId" DataType="{x:Type local:WorkListViewItem}">
<TextBlock Text="{Binding Path=Id}" Foreground="GreenYellow"/>
</DataTemplate>
<DataTemplate x:Key="DTd" DataType="{x:Type local:WorkListViewItem}">
<TextBlock Text="{Binding Path=d}" Foreground="BlueViolet"/>
</DataTemplate>
Data Array:
<x:Array Type="{x:Type local:WorkListViewItem}" x:Key="wvlis">
<local:WorkListViewItem Id="1" Namey="Fred" d="True"/>
<local:WorkListViewItem Id="2" Namey="Beef" d="True"/>
<local:WorkListViewItem Id="3" Namey="Pork" d="False"/>
</x:Array>
ListView:
<ListView x:Name="listv" ItemsSource="{StaticResource wvlis}">
<!--This actually works-->
<ListView.View>
<GridView>
<GridViewColumn Header="Namey" Width="80" CellTemplate="{StaticResource DTNamey}"/>
<GridViewColumn Header="Length" Width="60" DisplayMemberBinding="{Binding Namey.Length}"/>
<GridViewColumn Header="D?" Width="60" CellTemplate="{StaticResource DTd}"/>
</GridView>
</ListView.View>
</ListView>
Now this all does exactly what I want it to:
(I lack the reputation to post images, but trust me, it really does!)
It displays the 3 columns and a row for each element in the data array.
Changes to the textboxes for Namey are reflected in the second column (Length), and all is well in the world.
The Issue:
However, that same ListView definition, when placed in a Control Template, displays no items. The columns display just fine, but no rows are shown.
Window Resources:
<!--This does not work-->
<ControlTemplate TargetType="ListView" x:Key="cmonnow">
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Namey" Width="80" CellTemplate="{StaticResource DTNamey}"/>
<GridViewColumn Header="Length" Width="60" DisplayMemberBinding="{Binding Namey.Length}"/>
<GridViewColumn Header="D?" Width="60" CellTemplate="{StaticResource DTd}"/>
</GridView>
</ListView.View>
</ListView>
</ControlTemplate>
Inside Grid:
<ListView x:Name="listv" ItemsSource="{StaticResource wvlis}" Template="{StaticResource cmonnow}">
</ListView>
My Efforts:
I have tried adding ItemsPresenter controls in the columns, which clashes with the column header definitions.
I've tried adding the ItemsPresenter under:
<ListView.Items>
<ItemsPresenter/>
</ListView.Items>
I've tried setting the ListView's data context to: (totally incorrect, I'm sure)
<ListView DataContext="{Binding local:WorkListViewItem}">
I've even tried defining the ListView's ItemTemplate property.
I have been working at figuring this out for 3 days, and decided to turn to the internet for help.
So my question would be; which ListView properties are required to display the items collection when using Control Templates?
Thank you for your time.
Matthew
Update 29/09/2015
I've found that placing ItemsPresenter and ContentPresenter elements directly under the ListView tag, adds blank items that are correctly styled via the data templates.
Breakthrough!
I have found that by binding the template's ListView's ItemsSource property, to the ItemsSource provided, all the items are displayed, and all columns are correctly populated.
<ControlTemplate TargetType="ListView" x:Key="cmonnow">
<ListView ItemsSource="{TemplateBinding ItemsSource}">
...
</ControlTemplate>
Figured it out
Boiling this down, it seems that the only component that is required to get a ListView control template to display data is the ItemsSource property of the template ListView.
The ListView View property does not need to be manually set in order to see items.
However, for custom objects such as mine, it may be advisable to override the ToString method, as this is what you will see without any further template definitions.
(On a side note, it was a little counter-intuitive to have to define a ListView inside a Control Template with its TargetType property set to "ListView", but you could count this as a requirement too)

WPF binding to CurrentItem

I have a WPF model which contains a viewsource, a listview, and a button, which is meant to execute an ICommand in the viewmodel for the item selected in the listview.
<Window.Resources>
<CollectionViewSource x:Key="teachingSessionsViewSource" d:DesignSource="{d:DesignInstance {x:Type local:TeachingSessionListItem}, CreateList=True}"/>
</Window.Resources>
...
<GroupBox Header="All Sessions"
DataContext="{Binding Source={StaticResource teachingSessionsViewSource}}">
<StackPanel>
<ListView x:Name="TeachingSessionList"
ItemsSource="{Binding}"
SelectedItem="{Binding Path=/}">
<ListView.View>
<GridView ColumnHeaderContainerStyle="{StaticResource noHeaderStyle}">
<GridViewColumn DisplayMemberBinding="{Binding SessionDate}"/>
<GridViewColumn DisplayMemberBinding="{Binding PresenterInitials}" Width="30" />
</GridView>
</ListView.View>
</ListView>
<Button Content="Un-Assign" Margin="5,5" Command="{Binding Path=/UnAssignPresentation}"
</StackPanel>
</GroupBox>
The problem is that the ICommand UnAssignPresentation is always executed on the first item in the list, regardless of which item is selected in the listview. That is to say, the currentItem property of the CollectionViewSource does not seem to be bound to the ListView.
What am I doing wrong? Thank you very much.
Try adding IsSynchronizedWithCurrentItem="True" to your ListView.
Also, prefer binding listview to a collection in your view model and not to a resource.
You can then add a property to the view model for the selected item
and bind the listview's selected item to that property in the view model
and either bind the button's command to a command in the view model that acts on the current item,or pass the item you want to act on via the command parameter binded to the selected item.

How to get selected item of a nested listview?

i have a nested listview, i can bind the selected item of the basic listview to my viewmodel but not my selected item of the nested listview ( in the basic listview ) I just do:
this is my listview:
<ListView Height="155" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Visible" dd:DragDrop.IsDragSource="True"
dd:DragDrop.IsDropTarget="False" Margin="24,506,280,169" Background="#CDC5CBC5"
dd:DragDrop.DropHandler="{Binding}" SelectedItem ="{Binding Path=SelectedCluster,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" ItemsSource="{Binding Path=Clusters,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" >
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Titel" DisplayMemberBinding="{Binding Title}"/>
<GridViewColumn Header="Questions">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ListView DataContext="{Binding}" ItemsSource="{Binding ExaminationQuestions}" SelectedItem="{Binding Path=SelectedExaminationQuestionInCluster,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}">
<ListView.View>
<GridView>
<GridViewColumn Header="Description" DisplayMemberBinding="{Binding Question.Description}"/>
</GridView>
</ListView.View>
</ListView>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
Viewmodel:
public ExaminationQuestion SelectedExaminationQuestionInCluster
{
get { return selectedExaminationQuestionInCluster; }
set { selectedExaminationQuestionInCluster = value;
OnPropertyChanged("SelectedExaminationQuestionInCluster");
}
}
Someone who knows what i am doing wrong? If i set a breakpoint of the setter of selecteditem of the second listview. He just ignores that..
Thanks
My guess is the binding is probably incorrect. In your outer ListView, you bind to "Clusters". Your inner ListView is probably trying to bind to "SelectedExaminationQuestionInCluster" on the current Cluster. You can see if this is the case by using snoop. It's a valuable tool when debugging WPF apps. It will highlight broken bindings in red and tell you what's wrong with them.
If you want to bind to "SelectedExaminationQuestionInCluster" on the parent DataContext, you could use this syntax:
SelectedItem="{Binding Path=DataContext.SelectedExaminationQuestionInCluster,
ElementName=OuterListView}"
You'll have to give the outer ListView a name of course.
EDIT: I just realized this might not make sense. If each Cluster has its own collection of ExaminationQuestions, then each Cluster should also have a SelectedExaminationQuestion. The parent DataContext should not have any concept of a SelectedQuestion unless it is shared amongst all Clusters.

Display a ListBox with a List of enums

I'm trying to display a ListBox inside of a GridViewColumn that needs to bind to a List of enums (List<ResourceType> Cost). The GridViewColumn's ListView is already bound to a collection of objects and I'm not really sure the best way to go about displaying the ListBox. Any suggestions?
You can bind the ListBox to a list of enum values. An easy way to do that is to use the markup extension I posted here.
Then, you need to bind the SelectedItem of the ListBox to the property displayed in the GridViewColumn.
You should end up with something like that :
<GridViewColumn Header="Resource type">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ListBox ItemsSource="{local:EnumValues local:ResourceType}"
SelectedItem="{Binding SelectedResourceType}">
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
EDIT: I think I misread your question... If I understand correctly, each object displayed in the ListView has a Cost property of type List<ResourceType>, right ? (btw, the fact that ResourceType is an enum doesn't matter here). So you just need to bind the ListBox to the Cost property :
<GridViewColumn Header="Resource type">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding Cost}">
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>

Resources