Populating DataGridComboBox in Grid with SelectedItem - c#

I am trying to get a DataGridComboBoxColumn (Or DataGridTemplateColumn with a ComboBox) to populate a list of ports and default each row to the port that is already stored in the database. I have got a DataGridTemplateColumn with a ComboBox populating the Ports, but I cannot seem to get it to select what is already stored in the database.
I'm using Entity Framework and I have 2 tables, 'Route' and 'Port'. 'Route' has 2 foreign keys for a 'Destination Port' and a 'Arrival Port'.
I have 2 ObervableCollections, one for the list of ports, and another for the list of routes. In the routes collection there is a 'Port1' and 'Port2' for Destination/Arrival respectively.
This is what I currently have:
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.PortCollection, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="PortName"
SelectedItem="{Binding DataContext.RouteCollection, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
SelectedValue="{Binding DataContext.Port1, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"
SelectedValuePath="PortId">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
But as I said it is only listing the available ports, and is otherwise blank. Sorry if I haven't made myself too clear, I'm not very good at articulating, generally.
Here are my collections:
public ObservableCollection<Port> PortCollection { get; set; }
//List of Routes
private ObservableCollection<Route> _RouteCollection;
public ObservableCollection<Route> RouteCollection
{
get { return _RouteCollection; }
set
{
_RouteCollection = value;
Set(() => RouteCollection, ref _RouteCollection, value);
}
}
There's clearly something obvious wrong as this must be quite a common thing to do, but I've been going mad for 4 hours on this just getting this far! :(
Many thanks

"Port1" is supposed to be a property of the item in the DataGrid's Items collection. It should have the same type as the PortId property of the Port class.
You can then bind the SelectedValue property directly to it like this:
<ComboBox ItemsSource="{Binding DataContext.PortCollection, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="PortName"
SelectedValue="{Binding Port1}"
SelectedValuePath="PortId">
The default DataContext of an element in the CellTemplate of a DataGridTemplateColumn is the corresponding item in the Items/ItemsSource collection of the parent DataGrid.
Note that if the type of the Port1 property is Port you should use the SelectedItem property instead of SelectedValue:
<ComboBox ItemsSource="{Binding DataContext.PortCollection, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="PortName"
SelectedItem="{Binding Port1}">

Related

Datacontext not available in DataGridTemplateColumn

I have the following grid:
<DataGrid
x:Name="CandiesDataGrid"
ItemsSource="{Binding Candies}"
SelectedItem="{Binding SelectedCandy}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding CandySelectedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
<DataGridTextColumn KeyboardNavigation.IsTabStop="False" IsReadOnly="True" Width="100" Header="{l:LocText Candy_Prop1}" Binding="{Binding CandyInfo.Name}"/>
<DataGridTemplateColumn >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="IsConfirmed" Grid.Column="0"
Style="{StaticResource CandyCheckBox}"
IsChecked="{Binding IsConfirmed, Mode=TwoWay}"
Margin="-75 0 0 0"
Command="{Binding IsConfirmedCommand}">
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
My property uses the OnPropertyChanged. Not only it does not change the value of IsConfirmed but also does not executes the ICommand IsConfirmedCommand.
I searched on the internet and it seems DataGridTemplateColumn loses the ItemSource of the datagrid.
I did try to put RelativeSource in after the mode=TwoWay on my checkbox but it does not work.
Is there any way to have access to the ItemSource in my TemplateColumn?
EDIT:
//Properties
public ObservableCollection<Candy> Candies{ get; } = new ObservableCollection<Candy>();
public Candy SelectedCandy { get { return _selectedCandy; } set { SetProperty(ref _selectedCandy, value); } } //SetProperty acts as OnPropertyChanged
private Candy _selectedCandy;
//Constructor:
public CandyClass()
{
IsConfirmedCommand = new DelegateCommand(IsConfirmedCommand_Execute);
}
//Method
private void IsConfirmedCommand_Execute()
{
//Doing something
}
Inside your CellTemplate, the DataContext is the DataGrid row, whatever that may be (Candy in this case). So by default, that Candy instance will be the Source property of any Binding in that DataTemplate. That's where the binding will look for the property named in the Path (IsConfirmed and IsConfirmedCommand, in this case).
That's what you want: You've got more than one row in the grid, and the row is what you care about in a cell, usually. That or the field: But very often a cell template will want to look at more than one field, so they give you the whole row.
But in this case, you want to go back up and grab something off the parent viewmodel. Viewmodels have no natural parent/child hierarchy, though you could give them one if you wanted: Candy could have a Parent property that had reference to the viewmodel that owns the Candies collection. If you did, you could bind like this:
Command="{Binding Parent.IsConfirmed}"
But that's not common practice. I don't know if it's a particularly great idea or not.
One reason we don't need to do that is we can tell the binding to use a different source instead. UI elements do have a natural parent/child hierarchy, and bindings can navigate it. If you’re doing things right, your parent viewmodel will be the DataContext of something up there somewhere.
{Binding Path=DataContext.IsConfirmed,
RelativeSource={RelativeSource AncestorType=DataGrid}}
"Walk the UI tree upwards until you find a DataGrid. That's your source. Now, once you have a source, find the source object's DataContext property, if any. If it's got a DataContext, take the value of DataContext and look on that object for some property called IsConfirmed."
DataGrid has a DataContext property. Since your binding to Candies worked, we know that DataContext must be your class that has a Candies property. You assure me that class has IsConfirmed as well.
Hence:
<DataTemplate>
<CheckBox
Style="{StaticResource CandyCheckBox}"
IsChecked="{Binding DataContext.IsConfirmed,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
Margin="-75 0 0 0"
Command="{Binding DataContext.IsConfirmedCommand,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
/>
</DataTemplate>

Disable selection of specific item in DatagridComboBox

Background:
I have a Datagrid with some Measurements and this Measurements we can Approve and Block.
Now we have for this a new Type, like "Cancelled". But this Type is only needed by Server and for displaying it to Customer.
But the Customer should not be able to select this "Cancelled" but the other 2 Types he should have to select.
The List get all different elements from Database (3 entries).
Firstly i tried to remove the Cancelled Item from the ApprovementCollection, but then it displayed a empty field instead of "Cancelled".
Question:
Is it possible, to disable only one of this 3 Items in the Bounded List of the Itemsource Property from the DataGridComboBoxColumn?
Disabled or Not Displayed in the Selection Menu is that what i have to do.
(Only "Freigabe" and "GESPERRT")
View:
<DataGridComboBoxColumn ClipboardContentBinding="{x:Null}"
DisplayMemberPath="ApprovementText"
Header="{x:Static trans:Translations.ClearenceHeader}"
ItemsSource="{Binding Source={StaticResource PossibleComponentMeasurementApprovements}}"
SelectedItemBinding="{Binding Approvement,
UpdateSourceTrigger=PropertyChanged}" />
Viewmodel:
private IEnumerable<ComponentMeasurementApprovement> possibleComponentMeasurementApprovements;
public IEnumerable<ComponentMeasurementApprovement> PossibleComponentMeasurementApprovements {
get { return possibleComponentMeasurementApprovements; }
set {
possibleComponentMeasurementApprovements = value;
OnPropertyChanged();
}
}
Thanks for your Help
This is possible writing a DataGridTemplateColumn for your cell instead of using the DataGridComboBoxColumn. Just add a property Enabled to your ComponentMeasurementApprovement class. This property indicates if the user is allowed to select the approvement.
Now create a new DataGridTemplateColumn containing a ComboBox as template. It is possible to bind IsEnabled of every ComboBox item to a proeprty by styling them via ItemContainerStyle.
Here is the code:
<DataGridTemplateColumn Header="CustomCell">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Source={x:Static local:ViewModel.PossibleComponentMeasurementApprovements}}"
DisplayMemberPath="ApprovementText"
SelectedItem="{Binding Approvement}">
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="IsEnabled" Value="{Binding Enabled}"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Here is the result:
Since the second item is disabled, it's not possible to select it but an already selected item keeps selected.

ComboBox in DataGrid: ItemSource bound to dynamic resource, how to set default row?

I'm working in a C# project [for school], using WPF and implementing MVP. In this code, I've got a DataGrid showing a list of divers. The first column is the Name, and the second column shall show 'DivingType'. DivingType is a built in object, which has a property ID, such as 103A. There are about 400 of these, stored in a list, and each Diver ('row') has a Dives (List<Dive>) Property, and each of these Dives has a divingType property.
What we want to have, is that this column will by default show the DivingType.ID associated with the diver, but that the dropdown list shall contain ALL diving types, such that you shall be able to change it from there [and update the diver object]. To further complicate it, this is one of many views which we add to our window as UserControls.
With that said, here is the code. I've tried to cut out unnecessary clutter which I'm certain has no impact on the result.
<Window ...>
<Window.Resources>
<local:Presenter x:Key="myPresenter"/>
</Window.Resources>
<DockPanel DataContext="{StaticResource myPresenter}">
<UserControl ...>
<DataGrid ItemsSource="{Binding DiverList}" x:Name="datagrid">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="1*" Binding="{Binding Name}"/>
<DataGridTemplateColumn Header="Diving type" Width="1*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=UserControl},
Path=DataContext.DivingTypes}"
DisplayMemberPath="ID"
SelectedValue="{Binding Dives[0]}"
SelectedValuePath="divingType">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>
</DockPanel>
</Window>
When the program runs I get all DivingTypes.ID inside the combobox, but no selected value. The code does not put any related errors into the output window. I believe that what happens is that it calls DivingType.Equals but passing the DataContext for the row (the Diver) instead of the SelectedValuePath which I specify. Any way to override this behaviour inside XAML? Or is there an easier way to achieve this?
EDIT:
I've since edited the code posted above to be:
<ComboBox ItemsSource="{
Binding RelativeSource={
RelativeSource AncestorType=UserControl
},
Path=DataContext.DivingTypes
}"
SelectedValue="{
Binding Dives[0].divingType, Mode=TwoWay
}"
/>
This makes the correct value show in the combobox at the start, DivingType.ID is loaded from the Diver.Dives[0].divingType, but it still does not set the property when I select a new value in the dropdown box.
Use UpdateSourceTrigger=PropertyChanged.
Explanation here.
Have you tried to implemented INotifyPropertyChanged in your viewmodel and then raise the PropertyChanged event when the SelectedValue gets set.
If this is not working, can you set the SelectedValue
SelectedValue="{Binding Path=divingType, Mode=TwoWay}"

Remove elements from window based on Combobox selection xaml

I have the following situation: inside xaml I get values from a database and fill the combobox..if the selected item in the combobox has a value "x" I want to hide some elements from the working window..thx for your tips
<TextBlock Text="XYZ:"/>
<ComboBox ItemsSource="{Binding DataContext.KeyLists.XYZ,
RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
SelectedValuePath="XYZId"
SelectedValue="{Binding XYZId, Mode=TwoWay}"
DisplayMemberPath="Name" />
There are many ways to solve this.
you can make IValueConverter to convert selection values to Visibility, apply to each control with different converter parameter
you can write styles with triggers for the convtols
you could (not recommended) handle this in code

Binding up using data binding

I've an
ObservableCollection<Products> products
in my Model class and another
ObservableCollection<Foo> foo
in the ViewModel class.
I've binded the products to a Developer Express GridControl and I've a column in that grid that is a ComboBox.
How I can bind the foo collection in the ComboBox?
Here's the way I've binded the products to the GridControl
<dxg:GridControl ItemsSource="{Binding Path=Model.Products}" ...
Something like this should work:
<ComboBox ItemsSource="{Binding Path=DataContext.foo, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type dxg:GridControl}}}"/>

Categories

Resources