I have an items control bound to a collection of type 'TypeA'. This type 'TypeA' has a collection of type 'TypeB'.
In the items control dataTemplate, I have a datagrid which displays a row for each item of the TypeB collection and for each row one column displaying the value of the variable 'VariableOfTypeB'.
It works fine.
Now, I would like to display another datagrid below which binds to the current item of the collection of type 'TypeA' and which would display the value of the variable 'VariableOfTypeA'. This I cannot seem to achieve.
I can access 'VariableOfTypeA' outside of the datagrid, in a label for instance.
Would you have any tip please?
<ItemsControl ItemsSource="{Binding TypeACollection}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<DataGrid ItemsSource="{Binding TypeBCollection}">
<DataGrid.Columns>
<DataGridTextColumn Width="Auto"
Binding="{Binding VariableOfTypeB />
</DataGrid.Columns>
</DataGrid>
<!-- cannot make this work -->
<DataGrid ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridTextColumn Width="Auto"
Binding="{Binding VariableOfTypeA />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The ItemsControl does not have a SelectedItem which is what you are looking for in a sense.
You would be better suited to use a DataGrid or ListView which has a selected row property (of/from your A collection) which then could be bound to in the subgrid to show the alternate information.
Related
I have two WPF elements bound to the same ObservableCollection. One is a Datagrid and one is a ListBox. When the Datagrid is used to sort on a column (using the built-in column headers) the action puts the items in the listbox into the same order. In other words, it seems that the sorting action in the Datagrid affects the ordering of the underlying collection. Is there a way to disable this behavior?
Here is the XAML for the Datagrid:
<DataGrid
IsReadOnly="True">
>
<DataGrid.Columns>
<DataGridTextColumn
Binding="{Binding no}" >
<DataGridTextColumn.Header>
<TextBlock>
File<LineBreak/>No.
</TextBlock>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn
Header="Name"
Binding="{Binding fileName}" />
<DataGridTextColumn Binding="{Binding channels}" >
<DataGridTextColumn.Header>
<TextBlock TextAlignment="Center">
Channels<LineBreak/>[#]
</TextBlock>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
and the Listbox:
<ListBox
SelectedIndex="{Binding fileListSelectedIndex}"
SelectedItem="{Binding fileListSelectedItem}"
>
<ListBox.Resources>
<DataTemplate DataType="{x:Type local:FileListItem}">
<TextBlock Text="{Binding Path=fileName}"/>
</DataTemplate>
</ListBox.Resources>
</ListBox>
finally, the binding code:
filelist.ItemsSource = vm.fileList;
multiFileParamGrid.ItemsSource = vm.fileList;
ItemsSource uses special wrapper type (ICollectionView) when binding to some sequence or collection. That wrapper provides sorting functionality. The default wrapper object is obtained from CollectionViewSource.GetDefaultView method. When two ItemsControls (DataGrid and ListBox) bind to the same collection (vm.fileList), they (and any other code) will receive the same wrapper object.
But it is possible to create a different instance of wrapper on purpose:
filelist.ItemsSource = vm.fileList;
multiFileParamGrid.ItemsSource = new ListCollectionView(vm.fileList);
Currently i bind to a List<T> so i have to do specific set foreach Column a separate DataTemplate
like this:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock TextAlignment="Center"
Text="{Binding ObColl[1].Std, UpdateSourceTrigger=PropertyChanged}"
Background="{Binding ObColl[1].DienstColor, TargetNullValue=Transparent,FallbackValue=Transparent}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
but i want is to create the DataTemplate one time as Resources
<DataGrid.Resources>
<DataTemplate x:Name="MyCellTemplate">
<TextBlock TextAlignment="Center"
Text="{Binding Std, UpdateSourceTrigger=PropertyChanged}"
Background="{Binding DienstColor, TargetNullValue=Transparent,FallbackValue=Transparent}" />
</DataTemplate>
</DataGrid.Resources>
and use it like
<DataGridTemplateColumn CellTemplate="{StaticResource MyCellTemplate} ??{Binding ObColl[1]}??"/>
But to do so i need to specific the DataContext (ObColl[Idx]) in my DataGridTemplateColumn
but how do i do this?
EDIT
the xaml should look like :
<DataGrid Name="dataGrid1"
ItemsSource="{Binding Itemlist, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Resources>
<DataTemplate x:Key="MyCellTemplate">
<TextBlock TextAlignment="Center"
Text="{Binding Std, UpdateSourceTrigger=PropertyChanged}"
Background="{Binding DienstColor, TargetNullValue=Transparent, FallbackValue=Transparent}" />
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<!-- Column 1 -->
<DataGridTemplateColumn CellTemplate="{StaticResource MyCellTemplate}"
DataContext={Binding ObColl[0]}/>
<!-- Column Header 2 -->
<DataGridTemplateColumn CellTemplate="{StaticResource MyCellTemplate}"
DataContext={Binding ObColl[1]}/>
</DataGrid.Columns>
</DataGrid>
the DataContext={Binding ObColl[1]} is the problem part because it doesn't exist ....
Ok, here is my understanding of you requirement... you have a MyRow class with two properties; MyRowheader and MyCellList. You want to display the MyRowheader value and one value from the MyCellList collection on each row of your DataGrid. This is how I would do that:
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding YourCollection}">
<DataGrid.Columns>
<DataGridTextColumn Header="Header" Binding="{Binding MyRowheader,
UpdateSourceTrigger=PropertyChanged}" />
<DataGridTemplateColumn Header="Cell list">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyCellList[1].Std, UpdateSourceTrigger=
PropertyChanged}" Background="{Binding MyCellListl[1].DienstColor, TargetNullValue=
Transparent, FallbackValue=Transparent}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Please let me know if I have misunderstood your requirement.
UPDATE >>>
So I did misunderstand your requirement. It seem as though you want one value from your MyCellList collection in each column, not row, of the DataGrid. In that case, my answer would be no, you can't setup your DataGrid.Columns using a DataTemplate or any other XAML saving feature. XAML is a verbose language... there are a few ways of writing it more efficiently, but not many. You will often find repeated code on XAML pages.
The only way that I can think of that you could write less code would be if you dynamically generated the columns from code. You can find a basic example of that in the Dynamically add Columns to DataGrid in wpf post. I don't know how much time that will save you though really.
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.
Need a little help.
In XAML, I've got the following layout:
<DataGrid Name="outerGrid" ItemsSource="{Binding fullData}">
<DataGrid.Columns>
<DataGridTextColumn Header="Something" Binding="{Binding something}" />
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid Name="innerData" ItemsSource="{Binding innerData}">
<DataGrid.Columns>
<DataGridTextColumn Header="Something" Binding="{Binding something}" />
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
<DataGrid Name="fullDetailGrid" ItemsSource="{Binding ??????? }">
<DataGrid.Columns>
<DataGridTextColumn Header="Something" Binding="{Binding something}" />
</DataGrid.Columns>
</DataGrid>
This is the structure that I would like to preset, I know that if I do a three level depth grid this is easy, but I have a business requirement to have this kind of structure.
Is there anyway to do this without code behind and just do a relativesource binding of the inner grid
Thank you
I've had a similar issue with a topic where my ItemsSource of a parent control had been bound to a property within my ViewModel (which was bound to the DataContext), but where I needed the full ViewModel to bind to another property there. It then had the following syntax (in xaml):
<TextBlock Text="Documents" Visibility="{Binding ElementName=PageRoot, Path=DataContext.IsVisible }" />
As you can see, I bound my item to the PageRoot (where the DataContext had been bound to the ViewModel) and then directed it to my boolean indicating the visibility (with a converter obviously, but I removed that for reading purposes).
For your purpose: You could try binding the ItemsSource of your fullDetailGrid to the innerData source (though you might use to define that Element with x:Key instead of Name only). Then you could point it to the property which holds the desired value for your fullDataGrid. Syntax would be more or less the following (assuming you would use x:Key innerData on the nested datagrid):
<DataGrid Name="fullDetailGrid" ItemsSource="{Binding ElementName=innerData, Path=YourDataContext.YourCollection }">
I have 3 tables:
Item - which is the DataContext - it has a navigation column Group
Group - has a navigation column Category.
I want to have in the DataGrid both (Category & Group) columns and when I choose a category it should display in the group col only the Category.Groups.
Here is the code I am working on:
<tk:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding}">
<tk:DataGrid.Columns>
<!--Works-->
<tk:DataGridComboBoxColumn
Header="Categroy"
DisplayMemberPath="Title"
SelectedValuePath="CategoryId"
SelectedValueBinding="{Binding Group.Category.CategoryId}"
ItemsSource="{Binding Context.Categories,
Source={x:Static Application.Current}}"
/>
<!--Look at these two things:-->
<!--This does work-->
<tk:DataGridTemplateColumn>
<tk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ItemsControl
ItemsSource="{Binding Group.Category.Groups}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type data:Group}">
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</tk:DataGridTemplateColumn.CellTemplate>
</tk:DataGridTemplateColumn>
<!--But this does NOT work, even it's the same source-->
<!--Notice I even tried a dummy converter and doesnt reach there-->
<tk:DataGridComboBoxColumn
Header="Group"
DisplayMemberPath="Title"
SelectedValuePath="GroupId"
ItemsSource="{Binding Group.Category.Groups,
Converter={StaticResource DummyConverter}}"
SelectedValueBinding="{Binding Group.GroupId}"
/>
</tk:DataGrid.Columns>
</tk:DataGrid>
Update
Would you say the problem is that the ItemsSource property cannot be set to a non-static Binding?
I suspect so because even I set the ItemsSource to {Binding} with the DummyConverter it doesn't stop in the converter; and in the Category ComboBox it works fine.
The columns in the datagrid don't have a datacontext, as they are never added to the visual tree. sound a bit weird but have a look at vince's blog, its got a good example of the visual layout. once the grid is drawn the cells have a data context and you can set the combo boxes items source in them using normal bindings (not static resources..)
You can access the combo box items source as such:
<dg:DataGridComboBoxColumn>
<dg:DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding Path=MyBindingPath}" />
</Style>
</dg:DataGridComboBoxColumn.EditingElementStyle>
</dg:DataGridComboBoxColumn>
Have a look here and also here for some code. You will also need to set the items source for the non edting element as in this post
I was using MVVM and I wanted to bind the ItemSource of the column to a collection of objects in the window data context. I must have tried 10 different ways and nothing worked until I found this answer.
The trick is to define a CollectionViewSource outside the grid and then reference it inside the grid using StaticResource. For example,
<Window.Resources>
<CollectionViewSource x:Key="ItemsCVS" Source="{Binding MyItems}" />
</Window.Resources>
<!-- ... -->
<DataGrid ItemsSource="{Binding MyRecords}">
<DataGridComboBoxColumn Header="Column With Predefined Values"
ItemsSource="{Binding Source={StaticResource ItemsCVS}}"
SelectedValueBinding="{Binding MyItemId}"
SelectedValuePath="Id"
DisplayMemberPath="StatusCode" />
</DataGrid>