I have a window with a datagrid getting his items from an ObservableCollection
and I have some field on the side which I want to set from the selected row on the datagrid.
Problem is when I select a row the fields they are not set; how do they get set?
View
<Window x:Class="videotheque.View.FilmView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:videotheque.View"
xmlns:vm="clr-namespace:videotheque.ViewModel"
mc:Ignorable="d"
Title="Vos films" Height="480" Width="900">
<Window.DataContext>
<vm:FilmViewModel/>
</Window.DataContext>
<Grid>
<DataGrid SelectedItem="{Binding Path=SelectedMovie, Mode=TwoWay}" ItemsSource="{Binding ListFilm, Mode=TwoWay}" CanUserAddRows="False" AutoGenerateColumns="False" Width="499" HorizontalAlignment="Left" Margin="10,0,0,68" Height="371" VerticalAlignment="Bottom">
<DataGrid.Columns>
<DataGridTextColumn Header="Titre" Binding="{Binding Titre}" Width="150"/>
<DataGridTextColumn Header="Durée" Binding="{Binding Duree}"/>
<DataGridTextColumn Header="Age Min." Binding="{Binding AgeMinimum}"/>
<DataGridTextColumn Header="Langue" Binding="{Binding LangueMedia}" Width="50"/>
<DataGridTextColumn Header="Sous titres" Binding="{Binding SousTitres}" Width="60"/>
<DataGridTemplateColumn Header="Modifier">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button CommandParameter="{Binding Id}"
Command="{Binding Path=DataContext.ModifierLeMediaCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
Modifier
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Supprimer">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button CommandParameter="{Binding Id}"
Command="{Binding Path=DataContext.SupprimerLeMediaCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
Supprimer
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Label Content="Titre :" HorizontalAlignment="Left" Margin="520,11,0,0" VerticalAlignment="Top" Width="39"/>
<TextBlock Text="{Binding SelectedMovie.Titre, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="568,16,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="0.362,-1.709"/>
<CheckBox IsChecked="{Binding SelectedMovie.Vu, Mode=TwoWay}" Content="Vu" HorizontalAlignment="Left" Margin="709,17,0,0" VerticalAlignment="Top"/>
<Label Content="Note :" HorizontalAlignment="Left" Margin="521,42,0,0" VerticalAlignment="Top"/>
<TextBlock HorizontalAlignment="Left" Margin="568,47,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top"/>
<Label Content="Synopsis :" HorizontalAlignment="Left" Margin="521,82,0,0" VerticalAlignment="Top"/>
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" Margin="525,113,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Height="95" Width="209"/>
<Label Content="Commentaire :" HorizontalAlignment="Left" Margin="519,223,0,0" VerticalAlignment="Top"/>
<TextBlock HorizontalAlignment="Left" Margin="525,254,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Height="128" Width="218"/>
<CheckBox IsChecked="{Binding SelectedMovie.SupportPhysique, Mode=TwoWay}" Content="Support physique" HorizontalAlignment="Left" Margin="709,56,0,0" VerticalAlignment="Top"/>
<CheckBox IsChecked="{Binding SelectedMovie.SupportNumerique, Mode=TwoWay}" Content="Support numérique" HorizontalAlignment="Left" Margin="709,88,0,0" VerticalAlignment="Top"/>
<Button Command="{Binding Path=DataContext.AjouterFilmCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
Content="Ajouter un film" HorizontalAlignment="Left" Margin="10,405,0,0" VerticalAlignment="Top" Width="182"/>
</Grid>
ViewModel (did not past all code, there is juste some command function for the buttons which works):
class FilmViewModel
{
public ObservableCollection<Media> ListFilm { get; set; }
private ICommand _modifierLeMediaCommand;
private ICommand _supprimerLeMedia;
private Media SelectedMovie;
public FilmViewModel()
{
this.ListFilm = new ObservableCollection<Media>();
this.LoadData();
}
public async void LoadData()
{
var context = await DataAccess.VideothequeDbContext.GetCurrent();
foreach (Media f in context.Medias.Where(m => m.TypeMedia.ToString() == "Film").ToList())
{
this.ListFilm.Add(f);
}
}
}
And the model :
public class Media
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime DateSortie { get; set; }
public TimeSpan Duree { get; set; }
public string Titre { get; set; }
public bool Vu { get; set; }
public int Note { get; set; }
public string Commentaire { get; set; }
public string Synopsis { get; set; }
public ETypeMedia.TypeMedia TypeMedia { get; set; }
public int AgeMinimum { get; set; }
public bool SupportPhysique { get; set; }
public bool SupportNumerique { get; set; }
public string Image { get; set; }
public ELangue.Langue LangueVO { get; set; }
public ELangue.Langue LangueMedia { get; set; }
public ELangue.Langue SousTitres { get; set; }
[InverseProperty(nameof(GenreMedia.Media))]
public List<GenreMedia> Genres { get; set; }
[InverseProperty(nameof(PersonneMedia.Media))]
public List<PersonneMedia> Personnes { get; set; }
[InverseProperty(nameof(Episode.Media))]
public List<Episode> Episodes { get; set; }
}
I did hear about INotifyPropertyChanged
, but it looks like so long to do with what I have so I dont know...
Take your control which has a binding that says ="{Binding SelectedMovie.Vu, ...}"
.
Remember that Binding is just reflection into the instance object of what is defined which is to go to SelectedMovie.Vu
.
The SelectedMovie
property is set to private and reflection into that fails right there (issue #1).
So let us make SelectedMovie
public. It still fails! Even when the selection changes the binding does not get the current data. Why?
The why is that when the control is placed on the screen it attempts a binding at that time which the property is Null
. So it does not show anything.
When the property changes, the binding is still failing because it is not receiving an event for a change so it can replace the null value.
Why?
The property does not follow INotifyPropertyChanged
so no event announces that a change has been made because the control's binding will listen to a change event for SelectedMovie
, but SelectedMovie
never announces a change.
But there is a possible third problem even after the SelectedMovie
reports a change by using INotifyPropertyChanged
. Because the binding is looking for a child actual change notification from .Vu
.
The .Vu
does not announce a change because it also doesn't do InotifyPropertyChanged
. Even if it did...it doesn't announce a change when SelectedMovie
is set. How would it know?
You can make your binding system work, but you are going to have to do some extra notify events when the top level object is set. What you should use is a binding to the named DataGrid
itself. Here is an example using a similar ListBox
:
Example
In our Window
or Page
or Control
I am going to setup some data in the XAML for our example.
Data
<Window.Resources>
<model:Orders x:Key="Orders">
<model:Order CustomerName="Alpha"
OrderId="997"
InProgress="True" />
<model:Order CustomerName="Beta"
OrderId="998"
InProgress="False" />
<model:Order CustomerName="Omega"
OrderId="999"
InProgress="True" />
<model:Order CustomerName="Zeta"
OrderId="1000"
InProgress="False" />
</model:Orders>
</Window.Resources>
ListBox
Then I am going to host/bind the data in a ListBox
in which every line will be a TextBox
to show the CustomerName
.
<ListBox ItemsSource="{StaticResource Orders}" x:Name="lbOrders">
<ListBox.Resources>
<DataTemplate DataType="{x:Type model:Order}">
<TextBlock Text="{Binding Path=CustomerName}" />
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=InProgress}" Value="True">
<Setter Property="Foreground" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
So we have a listbox that shows the data:
Show Selected Item's Order Number
So now we add another control under it which will show the user's selected item's order number. We will bind by using the ElementName
which will point to our listbox control's SelectedItem
dependancy property with the name of the control we have given which is lbOrders
. On that property it will hold the selected instance and we will drill down to OrderId
.
<Label Content="{Binding SelectedItem.OrderId, ElementName=lbOrders}"/>
So when we select Omega
we get "999" shown.