Search code examples
c#wpfmvvmdatagrid

Binding data from datagrid to ui components


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


Solution

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

    enter image description here

    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.

    enter image description here