mvvm ComboBox selection in Listview - wpf

I am trying to display a collection of objects in a wpf-Listview and provide a combobox for selection of values from an other collection.
Some code to illustrate my problem:
XAML: (Window gets its DataContext set in app.xaml.cs)
<ListView ItemsSource="{Binding Path=Books}" SelectedItem="{Binding Path=CurrentBook}">
<ListView.View>
<GridView>
<GridViewColumn Header="author" DisplayMemberBinding="{Binding author}" />
<GridViewColumn Header="title" DisplayMemberBinding="{Binding title}" />
<GridViewColumn Header="genre">
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Genres}" IsSynchronizedWithCurrentItem="true" DisplayMemberPath="genreName" SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CurrentBook_Genre}">
<ComboBox.ItemTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding genreName}" />
<TextBlock Text="{Binding genreDescription}" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
For now is "CurrentBook_Genre" a Property of the MainWindowViewModel, the according value of the Item shown in the ListView is set by its setter.
Like that the same ComboBox is shown in every "row" of the ListView.
I guess I need some kind of "EditValueTemplate" and "ShowValueTemplate" and a Selector of some kind, it would be fine, if the ComboBox gets shown only in the row which is in edit-mode - i guess this is a common thing for most DataDriven apps, so maybe there is a way more easy iam not aware of.
The only good explanation for this was
http://tech.pro/tutorial/857/wpf-tutorial-using-the-listview-part-3-in-place-edit
But they are working with dependency-properties, opposed to the ViewModel-wrapped models iam using.

There is a much easier way to get one control displayed when a grid row is edited and another when it is not edited than the example that you found. Try this instead:
<DataGrid ItemsSource="{Binding Books}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CurrentBook_Genre.genreName}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Genres}" IsSynchronizedWithCurrentItem="true" DisplayMemberPath="genreName" SelectedItem="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CurrentBook_Genre}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
This will display a ComboBox when the row is being edited and a plain old TextBlock when it is not.

Related

Binding between two DataTemplate works in one direction only

I have a DataGrid where there are Date0, Date1, and Date2 columns. I can change the columns with a ComboBox.
I started to use DataTemplate. The CellTemplate uses Date0InParameterEditingTemplate where I bind Date0 which is not simple DateTime but a class. Then this class is bound to DateEditingTemplate. I would like to use DateEditingTemplate later with Date1 and Date2 as well.
The data goes well to one direction, so the ComboBox displays the proper date. When I select a new value in the ComboBox then the SelectedItem changes. But the content of ContentControl remains unchanged.
<DataGridTemplateColumn Header="{x:Static r:Resource.Date0}"
CellTemplate="{StaticResource Date0InParameterEditingTemplate}"/>
<DataTemplate x:Key="DateEditingTemplate">
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}, Path=DataContext.ValidDateObjects}"
SelectedItem="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Date" Style="{StaticResource ComboBoxError}" IsEditable="False"
ItemStringFormat="{StaticResource DateFormatHU}" />
</DataTemplate>
<!--<DataTemplate x:Key="DateEditingTemplate">
<TextBlock Text="{Binding Date}"/>
</DataTemplate>-->
<DataTemplate x:Key="Date0InParameterEditingTemplate">
<ContentControl Content="{Binding Date0, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ContentTemplate="{StaticResource DateEditingTemplate}"/>
</DataTemplate>

How to handle click in ControlTemplate for ComboBox in WPF?

In a DataGrid I display data about financial transactions. One column is the Account with the xaml below. The CellEditingTemplate display the ComboBox with the accounts. The CellTemplate is tricky. I want to display the Account as a TextBlock. Previously I bound "Account.Name" but validating a nested property is not easy if Account is null (e.g. in a new row). So I decided to use ComboBox but with a ControlTemplate. Displaying values and validation works. The problem is that for click the CellEditingTemplate is not activated.
<DataGridTemplateColumn Header="{x:Static r:Resource.AccountName}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}, Path=DataContext.AccountObjects}"
SelectedItem="{Binding Account, ValidatesOnDataErrors=True}"
DisplayMemberPath="Name">
<ComboBox.Template>
<ControlTemplate TargetType="{x:Type ComboBox}">
<TextBlock Text="{TemplateBinding Text}"/>
</ControlTemplate>
</ComboBox.Template>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}, Path=DataContext.AccountObjects}"
SelectedItem="{Binding Account, ValidatesOnDataErrors=True}"
DisplayMemberPath="Name"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

How to use DisplayMemberPath in WPF CellTemplate?

In a DataGrid one of the columns is Account. I want to select AccountId but the ComboBox should display AccountNames. So I created the xaml below. It works as required but it has a drawback. If I change the account and go to another cell in the same row then AccountName is not updated yet. (If I leave the row then AccountName is calculated and updated based on the new AccountId.)
If I use only CellTemplate with the ComboBox then I evaded the problem but I do not like this solution because it is not nice to show the ComboBoxes when they are not needed. I could try to update AccountName when I leave the cell but my db view would do that and at this point there could be errors in the current row. So I would like to show the selected AccountName as a TextBlock in CellTemplate based on the AccountId. (I tried to put the ComboBox into the TextBlock but then the TextBlock does not just show the selected AccountName but the ComboBox itself.)
<DataGridTemplateColumn Header="{x:Static r:Resource.AccountName}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding AccountName, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBlockError}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}, Path=DataContext.AccountObjects}"
SelectedValue="{Binding AccountId, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}"
SelectedValuePath="Id"
DisplayMemberPath="Name"
Style="{StaticResource ComboBoxError}" IsEditable="True"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
You should bind to a Account property of your data object and implement the INotifyPropertyChanged interface. You may also want to set the UpdateSourceTrigger property to PropertyChanged:
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}, Path=DataContext.AccountObjects}"
SelectedItem="{Binding Account, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Name"
Style="{StaticResource ComboBoxError}" IsEditable="True"/>
The answer of #mm8 helped me a lot.
Until now there was only int AccountId in my model file.
I introduced Account Account navigation property which created foreign key in the db.
(I am using Enitity Framework.)
When the db is updated Account is not changed only AccountId.
(Otherwise there are problems with the db, EF think it should insert a new Account instead using the existing one.)
Now the setter of the Account property also changes the AccountId.
Account implements IEquatable
otherwise the initial value of the ComboBox is not displayed
Finally the xaml (SelectedItem="{Binding Account}" and Text="{Binding Account.Name}" is used)
<DataGridTemplateColumn Header="{x:Static r:Resource.AccountName}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Account.Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextBlockError}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}, Path=DataContext.AccountObjects}"
SelectedItem="{Binding Account, ValidatesOnDataErrors=True, UpdateSourceTrigger=LostFocus}"
DisplayMemberPath="Name"
Style="{StaticResource ComboBoxError}" IsEditable="True"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="ToolTip">
<Setter.Value>
<TextBlock Text ="{Binding Account.Name}" TextWrapping="Wrap" MaxWidth="300"/>
</Setter.Value>
</Setter>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>

WPF ListView selected item visible

I have a ListView that has its ItemsSource on binding with an ObservableCollection.
<ListView
Name="ShapesList"
ItemsSource="{Binding ChartViewModel.ShapeList}"
Grid.Row="1"
Margin="10,0,10,5"
SelectionMode="Multiple">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}, Path=DataContext.IsChecked, Mode=TwoWay}" Content="{Binding Path=Name, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="PdC" DisplayMemberBinding="{Binding Name}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
This window has a model responsible of the logic: it calculates the checked item of the ListView. What I need is that when the window is open, the checked item of the ListView is visible.
I tried with
ShapesList.ScrollIntoView(ChartViewModel.GetIndexOfSelectedROI());
but it doesn't work, even if the method returns the correct index.
Thanks in advance for any help!
According to MSDN ListView.ScrollInToView takes the object you want to make visible as its parameter, not the index.

Cascading Combobox in DataGrid using DataTemplate Cross-Cell Binding

If I place my cascading comboboxes inside the same DataTemplate in a WPF DataGrid cell - the binding works properly (via ElementName). However, from a UI perspective I want my comboboxes to physically reside in different cells, not the same datagrid cell. How do you make cross-cell binding work (between DataTemplates) using DataGridTemplateColumns? It seems the issue is that the second comboboxes' ItemsSource cannot find the ElementName for binding when the comboboxes exist in different DataTemplate columns.
This works....
<DataGrid x:Name="grdItems" AutoGenerateColumns="false" ItemsSource="{Binding Model}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Car Make / Model" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cbCarMake" SelectedItem="{Binding CarMake, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.CarMakes, Mode=TwoWay}" DisplayMemberPath="Name" SelectedValuePath="ID">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<mvvm:EventToCommand Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext.SelectCarMake}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<ComboBox SelectedItem="{Binding CarModel, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding ElementName=cbCarMake, Path=Tag.CarModels, Mode=TwoWay}" DisplayMemberPath="Name" SelectedValuePath="ID"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
This doesn't....
<DataGrid x:Name="grdItems" AutoGenerateColumns="false" ItemsSource="{Binding Model}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Car Make" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cbCarMake" SelectedItem="{Binding CarMake, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.CarMakes, Mode=TwoWay}" DisplayMemberPath="Name" SelectedValuePath="ID">
<i:Interaction.Triggers>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<mvvm:EventToCommand Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext.SelectCarMake}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</i:Interaction.Triggers>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Car Model" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding CarModel, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding ElementName=cbCarMake, Path=Tag.CarModels, Mode=TwoWay}" DisplayMemberPath="Name" SelectedValuePath="ID"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The trick with cross-data template binding is to use the ViewModel as a go-between. When properties change on the view model (an ObservableCollection in this case), you can trigger this notification to the dependent listeners (elements listening to PropertyChanged events).
The issue for me was that the View Model wasn't receiving the PropertyChanged notifications it should have been. I assumed that the mvvm-light ObservableObject automatically triggered PropertyChanged events for all properties.
You must use the mvvminpcset code snippet which explicitly raises property change events for the items in your ObservableCollection. Once the PropertyChanged events started firing, the ItemSource binding worked as seen below.
<DataGrid x:Name="grdItems" AutoGenerateColumns="false" ItemsSource="{Binding Model}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Car Make" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cbCarMake" SelectedItem="{Binding CarMake, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.CarMakes, Mode=TwoWay}" DisplayMemberPath="Name" SelectedValuePath="ID">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Car Model" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding CarModel, UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding CarMake.CarModels}" DisplayMemberPath="Name" SelectedValuePath="ID"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

Resources