How to handle click in ControlTemplate for ComboBox in WPF? - 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>

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 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>

mvvm ComboBox selection in Listview

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.

WPF DataGrid Element Name of other DataGridTemplateColumn

I have two Datagrid Template column.
1) Fist column is a Combobox
2) Second column with Extended IntergerUpDown control
I need to set the IntegerUpDown Maximum value based on the Combox box SelectedItem value.
Please help me how to accomplish this. Sample xaml below.
<Grid><DataGrid ItemsSource="{Binding List1}" Name="x1">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ColorTemplate">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.List2, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="Name" SelectedValue="{Binding ColourId}" SelectedValuePath="Id" Tag="{Binding Id}"
HorizontalAlignment="Stretch" x:Name="discussTemplate" VerticalAlignment="Stretch"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="UPDown" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<extToolkit:IntegerUpDown AllowSpin="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Minimum="0"
x:Name="updown"
Maximum="{Binding ???}" >
</extToolkit:IntegerUpDown>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
simply bind to the value of your itemsrow? so if ColourID is the property of your itemsrow which is set by your combobox. you can bind your Maximum to ColourID
<extToolkit:IntegerUpDown AllowSpin="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Minimum="0"
Maximum="{Binding ColourId}" >
</extToolkit:IntegerUpDown>

WPF Datagrid TemplateColumn Control Enable and Disable

I have Datagrid with two TemplateColumn.
First column is a Combobox and Second column with Extended IntergerUpDown control
I need to Enable/Disable the IntegerUpDown control based on the Combox box SelectedItem value.
Please help me how to accomplish this. Sample xaml below.
<Grid><DataGrid ItemsSource="{Binding List1}" Name="x1">
<DataGrid.Columns>
<DataGridTemplateColumn Header="ColorTemplate">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.List2, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}}"
DisplayMemberPath="Name" SelectedValue="{Binding ColourId}" SelectedValuePath="Id" Tag="{Binding Id}"
HorizontalAlignment="Stretch" x:Name="discussTemplate" VerticalAlignment="Stretch"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="UPDown" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<extToolkit:IntegerUpDown AllowSpin="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="Auto" Minimum="0"
x:Name="updown"
IsEnabled="????" >
</extToolkit:IntegerUpDown>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
This can be easily done with a DataTrigger on your ViewModel ColourId property.
As your SelectedValue property on the ComboBox is already binded, you can have a DataTrigger on your extToolkit:IntegerUpDown control that will set IsEnabled to True/False based on ColourId value on your ViewModel.

Resources