Using multiple ObjectDataProviders with the same control - wpf

I've just started to learn binding in WPF and am having some trouble with using multiple ObjectDataProviders with the same control.
I have two ObjectDataProviders :
Is used to get a list of customer locations from a database and is used to populate a TreeView and
Takes a location as a parameter and returns all the customers from that location, populating a listView.
I'd like to make it so that when I click on one of the TreeView items, that it would take the SelectedItem text as the parameter, use it to populate the listview.
<ObjectDataProvider
x:Key="getLocations"
ObjectType="{x:Type local:DataSetCreator}"
MethodName="getLocations"
/>
<ObjectDataProvider
x:Key="getCustomersFromLocation"
ObjectType="{x:Type local:DataSetCreator}"
MethodName="getCustomersFromLocation">
<ObjectDataProvider.MethodParameters>
<x:Static Member="System:String.Empty" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<TreeView HorizontalAlignment="Left"
Margin="12,12,0,12"
Name="treeView2" Width="186"
ItemsSource="{Binding Source={StaticResource getLocations}}" >
<TreeView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Country}" />
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<ListView x:Name="lstCustomers"
ItemsSource="{Binding Source={StaticResource getCustomersFromLocation}}" Margin="204,41,12,12">
<ListView.View>
<GridView>
<GridViewColumn Header="CustomerID"
Width="200"
DisplayMemberBinding="{Binding Path=CustomerID}" />
<GridViewColumn Header="Company Name"
Width="370"
DisplayMemberBinding="{Binding Path=CompanyName}" />
</GridView>
</ListView.View>
</ListView>
Is it possible to achieve this within the XAML, or do I need to use the code-behind?

ObjectDataProviders are not very flexible as they cannot be bound. Among other things you could bind to the SelectedItem of the TreeView and employ a Binding.Converter to get you the items right items based on that value.

Related

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>

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}"/>

Nested Datagrid in ListBox

I have a datagrid nested inside the ItemTemplate of a ListBox. I'm trying to display a tree like data structure using this. My classes are as follows.
The object in my data context contains a List<Section> named Sections, my ListBox is bound to this. Each Section contains a List<Item> named Items, the DataGrid in eac ItemTemplate is bound to this.
When I run the app, I get a null reference exception from the XAML at the line with the binding. Is there a better/alternative way of doing this, or am I missing a trick with the binding?
<Window.Resources>
<CollectionViewSource x:Key="SectionSource" /><!-- this is initialized and filled with an ObservableCollection<Section> Sections when the window loads-->
</Window.Resources>
<ListBox x:Name="lstIngredients" ItemsSource="{Binding Source={StaticResource SectionSource}}">
<ListBox.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<CollectionViewSource x:Key="itemsSource" Source="{Binding Items}"/>
</DataTemplate.Resources>
<DataGrid x:Name="dgItems" IsReadOnly="false" AutoGenerateColumns="False" SelectionMode="Single" SelectionUnit="FullRow" IsSynchronizedWithCurrentItem="True"
DataContext="{Binding}"
ItemsSource="{Binding Source={StaticResource Items}}"
EnableRowVirtualization="false"
VirtualizingStackPanel.VirtualizationMode="Standard"
<DataGrid.Columns>
<DataGridTemplateColumn Width="2*" Header="{lex:LocText ChickenPing.Shared:Strings:Measurement}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock x:Name="quantity" Text="{Binding Measurement}" TextTrimming="CharacterEllipsis" TextAlignment="Left"/>
<!-- Null reference on this line caused by the binding. If I set this to any DependencyProperty on an Item object, I get a null reference-->
</DataTemplate>
This need to be path
ItemsSource="{Binding Source={StaticResource Items}}"
ItemsSource="{Binding Path=PropertyThatIsCollection}"
And delete the DataContext line
I eventually tracked this down to an event which was set in one of the TemplateColumns. Switching the event from
<TextBlock x:Name="quantity" Text="{Binding Measurement}" GotFocus="txt_GotFocus" />
to
<Style x:Key="FocusableTextbox" TargetType="{x:Type TextBox}">
<EventSetter Event="GotFocus" Handler="txt_GotFocus" />
</Style>
...
<TextBlock x:Name="quantity" Text="{Binding Measurement}" Style={StaticResource FocusableTextbox} />

How to get mock data into listview during design time and real data at run time in WPF

I am using Visual Studio 2008 writing a WPF application. I am new to WPF and would like to be able to see the contents of my listview at design time so that I can see what I am doing in the xaml, but bind to my real data at run time.
My data is an observable collection of a simple model type object that exposes a few properties like Id, Title, Description, etc
At runtime I need to be able to get at the data source collection from the code so I can change the contents dynamically
Currently I have:
<Window x:Class="EktronDataUI.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:EktronDataUI"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider ObjectType="{x:Type local:SmartFormDefinitionProvider}" x:Key="formsProvider" MethodName="GetMockData" />
</Window.Resources>
<Grid>
<DockPanel>
<TextBlock DockPanel.Dock="Top">Hello WPF</TextBlock>
<ListView Name="myListView" ItemsSource="{Binding Source={StaticResource formsProvider}}">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" DisplayMemberBinding="{Binding Id}" />
<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Title}" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Grid>
</Window>
Which shows me my mock data at runtime, but not at design time. The listview is just a blank rectangle in the designer, though I do see the "Hello WPF" text
Whats the typical way of handling this?
Edit:
Should this show me my data at design time as it stands? I found that if I cut the listview from xaml and then paste it back in, I see my data, its not exactly displayed right, but its there. But the moment I build it disappears and doesnt come back
<Window x:Class="EktronDataUI.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:EktronDataUI"
xmlns:design_vm="clr-namespace:Company.Product.ViewModel.Design"
mc:Ignorable="d"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider ObjectType="{x:Type design_vm:MyListViewModel}" x:Key="DesignTime_DataSource" d:IsDataSource="True"/>
</Window.Resources>
<Grid d:DataContext="{StaticResource DesignTime_DataSource}>
<DockPanel>
<TextBlock DockPanel.Dock="Top">Hello WPF</TextBlock>
<ListView Name="myListView" ItemsSource="{Binding Path=ListItems">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" DisplayMemberBinding="{Binding Id}" />
<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Title}" />
</GridView>
</ListView.View>
</ListView>
</DockPanel>
</Grid>
Provide a subclass of the class you're using as datacontext and hook it up as a design time datacontext using the d namespace as in my example. I have MVVM project with a namespace for my viewmodels and each class has a subclass that I use for design. These subclasses have constructors that populate them with design time data. The datacontext for runtime can be set in codebehind or through datatemplating. The viewmodel has an observablecollection for the listview items, and in the design-viewmodel this is filled with sample data in the constructor.

How can I display different ContextMenus in WPF ListView GridView?

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.

Resources