How can I display different ContextMenus in WPF ListView GridView? - wpf

I have a ListView GridView with ListViewItems that represent different categories of items. I'd like to display a different ContextMenu for each category of item. I was hoping to do this using DataTemplates but I'm struggling. My TreeView has a DataTemplate per category and I can see how I can set a different ContextMenu for each there but I can't seem to get similar DataTemplates to work for my ListView. Am I barking up the wrong tree?
E.g. this is one of my DataTemplates for the TreeView:
<DataTemplate DataType="{x:Type viewModel:Cat1ViewModel}">
<StackPanel Orientation="Horizontal">
<Image Width="16" Height="16" Margin="3,0"
Source="..\Images\cat1.png"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
and I can add my ContextMenu to the StackPanel (I hope) and Bob's my uncle.
But the guts of the GridView looks like this:
<ListView.Resources>
<DataTemplate x:Key="image">
<Image Width="16" Height="16" Margin="-3,0,-3,0"
HorizontalAlignment="Center"
Source="{Binding Path=ObjectClass,
Converter={StaticResource imageConverter}}" />
</DataTemplate>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn Width="20"
CellTemplate="{StaticResource image}"/>
<GridViewColumn Width="140" Header="Name"
DisplayMemberBinding="{Binding Path=Name}"
infrastructure:GridViewSort.PropertyName="Name"/>
<GridViewColumn Width="140" Header="Type"
DisplayMemberBinding="{Binding Path=Category}"
infrastructure:GridViewSort.PropertyName="Category"/>
<GridViewColumn Width="400" Header="Description"
DisplayMemberBinding="{Binding Path=Description}"
infrastructure:GridViewSort.PropertyName="Description"/>
</GridView>
</ListView.View>
This imageConverter in the DataTemplate resource displays the appropriate icon for the category of the listViewItem.
I'm not sure where to start. So, first, is what I want to do possible? If so, can you get me started, please.
Also:
At the moment, each ListViewItem is backed by a viewModel - all categories use the same viewModel class.
Background:
The reason I want to display a different ContextMenu rather than changing the ContextMenu is that I'm using Prism and the ContextMenus will be Regions populated automatically by various modules.

I think you can do this with an ItemTemplateSelector, rather than setting the ItemTemplate property on your ListView, use the ItemTemplateSelector property. You have to create your own implementation of the ItemTemplateSelector class and define the logic so that it knows which template to use for each set of conditions, then you just need to create a set of templates and you should be good to go! There's a good tutorial on how to do this here.

Related

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)

Data binding to a ViewModel property from a collections Binding?

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>

WPF DataGrid RowDetailsTemplate with Multiple Images (MVVM)

Goal
To add multiple images in a DataGrid's RowDetails template using the MVVM standards.
Background
I have an inspection window with a DataGrid designed to hold a damaged item's description along with the initials of the inspector. What is more, this DataGrid's RowDetailsTemplate needs to hold the pictures that the inspector took of the damaged item (so there might be more than one picture of the damaged item).
Problem
I have a DamagedWindow.xaml designed to create my DamagedItem entries. It looks like this:
DataGrid (.XAML)
<DataGrid ItemsSource="{Binding Pictures}" SelectedItem="{Binding SelectedPicture}" AutoGenerateColumns="False" Grid.Column="1" Grid.Row="2" Margin="5" HorizontalAlignment="Stretch" Name="DataGrid1" VerticalAlignment="Stretch" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Titre" Binding="{Binding Name}" Width="*" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Image Height="100" Source="{Binding Path}" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
As you can see, my RowDetails template works fine in this DataGrid. I have a class named PicturesList which inherits an ObservableCollection Of my PictureModel(Name, Description, Path).
The text boxes that you see above the DataGrid are properties of my DamagedItemModel (Description, Initiales, PicturesList). So when the user clicks on the Accept (Checkmark) button, the DamagedItem is added to a DamagedItemsList which is then set as the ItemSource of the DataGrid from my MainWindow:
DataGrid (.XAML)
<DataGrid ItemsSource="{Binding Path=DamagedItems}" SelectedItem="{Binding Path=SelectedDamagedItem}" AutoGenerateColumns="False" Name="DataGrid1" Height="250" Margin="3" IsEnabled="True" CanUserAddRows="False" FontSize="16">
<DataGrid.Columns>
<DataGridTextColumn Header="Description" Width="*" Binding="{Binding Description}"/>
<DataGridTextColumn Header="Initiales" Width="70" Binding="{Binding Initiales}"/>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<Image Height="100" Source="{Binding Pictures.Path}" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Here when I select the row, I get an empty RowDetailsTemplate result ... Even though my object contains my images. See below for more details:
So here's my question, is it possible to add multiple images to a RowDetailsTemplate in a DataGrid while following MVVM standards? If it is, what am I doing wrong?
You can't bind that way. you need to do it like this:
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Pictures}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Height="100" Source="{Binding Path}"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
The reason your method was not working is that the Binding source is always one object which is set by Binding Path, and your Binding Path was Pictures.Path which leads to nothing because Pictures object does not have Path, it's its Items which have Path.
In general, Whenever you find yourself dealing with a collection of some kind think of a control which is suitable for showing a collection, like ListBox, DataGrid or the best of all ItemsControl.
Anything that goes inside ItemTemplate of these controls, have their DataContext automatically set to the correspondent item, so you don't have to worry about it. All you have to do is to set the ItemsSource to your collection and set the Binding Paths of things inside to the properties of the Type of that collection, So that it knows where to look for data for each item.
In this code you can think of it this way, Like you have some StackPanels, first one has : Image Source="{Binding Pictures(0).Path}", seconds one has Image Source="{Binding Pictures(1).Path}" and so on. this way all Binding Paths point to an object.

How to define an ItemTemplate for GridView (inside a ListView) so it can be used in multiple different ListViews?

I have some ListView's which bind to collections in the ViewModel. The type of items in those collections are the same (let's call them TypeA) which is a class exposing multiple simple type properties. I would like to display them in a GridView inside the ListView. Naturally, I would want to define a DataTemplate for this TypeA so that I don't have to duplicate the same XAML for each of the ListView's. All of the examples I've found so far define the ItemTemplate inside the ListView. How can I make a resource and let different ListView's refer to this single resource?
UPDATE:
I am posting my XAML code for better clarification. My original XAML code (simplified) looks like this:
<ListView ItemsSource="{Binding MYCOLLECTION}" SelectionMode="Extended" >
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Prop1}" >
<GridViewColumn.Header>
<Border >
<TextBlock Text="Prop1" />
</Border>
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Prop2}" >
<GridViewColumn.Header>
<Border >
<TextBlock Text="Prop2" />
</Border>
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Prop3}" >
<GridViewColumn.Header>
<Border >
<TextBlock Text="Prop3" />
</Border>
</GridViewColumn.Header>
</GridViewColumn>
...
</GridView>
</ListView.View>
</ListView>
I have many dependency properties set to the headers and that's why I put individual Header items as well as why my code is really long--hence why I'm seeking to use a datatemplate.
So my idea is that I can show different selections of the same type in the same way in different GridView's (because we want the column headers to display the names of the properties so we use GridView).
BTW those are not the only places I am presenting this data type, so I definitely need to specify a Resource key and restrict this to be used only for GridView. And because I want to display in a Grid way, I assume can't define a DataTemplate for my type and use it as ItemTemplate in a ListView.
You do not even have to define a resource key just set the DataType because that will be used as a key internally by WPF. Just make sure your datatemplates are visible from any control where you want to use it, they will be automatically applied. (Just for example you can define them at application level in the resources of App.xaml, but you probably have separate resource dictionaries):
<Application x:Class="WpfTest.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dataModel="clr-namespace:DataModel"
StartupUri="MainWindow.xaml">
<Application.Resources>
<DataTemplate DataType="{x:Type dataModel:GreekGod}">
<TextBlock Text="{Binding Path=Name}" Foreground="Gold"/>
</DataTemplate>
</Application.Resources>
</Application>
For your code you need to define templates for each property because you are using it as a grid and you need to set the cell template for each column after removing the DisplaymemberBinding:
<GridViewColumn CellTemplate="{StaticResource prop1Template}">
<GridViewColumn.Header>
<Border >
<TextBlock Text="Prop1" />
</Border>
</GridViewColumn.Header>
</GridViewColumn>
...
And put the resource in a visible place just as mentioned before like in the Application resources:
<Application.Resources>
<DataTemplate x:Key="prop1Template">
<TextBlock Text="{Binding Prop1}" Foreground="Red"/>
</DataTemplate>
</Application.Resources>
You can use a ColumnHeaderTemplate and a ColumnContainerStyle like this:
<GridView
ColumnHeaderTemplate="{StaticResource myHeaderTemplate}"
ColumnHeaderContainerStyle="{StaticResource myHeaderStyle}"
>
<GridViewColumn Header="Prop1" CellTemplate="{StaticResource prop1Template}"/>
...
where the resources are for example:
<Style x:Key="myHeaderStyle" TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="Background" Value="LightBlue"/>
</Style>
<DataTemplate x:Key="myHeaderTemplate">
<DockPanel>
<CheckBox/>
<TextBlock FontSize="16" Foreground="DarkBlue">
<TextBlock.Text>
<Binding/>
</TextBlock.Text>
</TextBlock>
</DockPanel>
</DataTemplate>
Just create the DataTemplate in the Controls/Windows resources as you would do in the ListViews ItemTemplate and add x:Key="someKey" to it.
Now you can refer to it like so:
<ListView ItemTemplate="{StaticResource someKey}"/>

how to dynamically create controls for GridView DataTemplate in WPF?

I have a GridView control:
<GridView>
<GridViewColumn Header="Name" Width="500">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding TypeName}" Header="Type" Width="100" />
</GridView>
for the first column("name"), I would like to have a mechanism that when every record is binded, will be a way(e.g. event), so that I can dynamically add controls to the StackPanel.
for example, my data has a column called AnimalType, if it is a cat, I will add an image to the StackPanel; if it is a cow, I will put a media element to play a movie; if it is a dog, I will put a hyper link, etc.
How can I do that?
Take a look at the DataTemplateSelector class....
like here:
http://www.wpftutorial.net/DataTemplates.html

Resources