2

When I am trying to bind Datacontext of Window by DataContext="{Binding RelativeSource={RelativeSource Self}}" it is not working, how if I do the same thing in code-behind, it is working quite fine. Am I wrong in assuming that is same as this.DataContext=this in constructor of the window in code-behind.

Full Code of XAML is...

  <Window x:Class="SampleDemoListBox.MainWindow"
    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:SampleDemoListBox"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525"
    DataContext="{Binding RelativeSource={RelativeSource Self}}"
    >
    <Window.Resources>
     <DataTemplate x:Key="ModelItemTemplate" >
      <StackPanel Margin="25" Orientation="Horizontal">
      <Image VerticalAlignment="Top" x:Name="ModelPicture" Width="150" 
        Source="{Binding PicturePath}"></Image>
        <Grid VerticalAlignment="Top">
           <Grid.RowDefinitions>
                 <RowDefinition></RowDefinition>
                  <RowDefinition></RowDefinition>
                  <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                   <ColumnDefinition></ColumnDefinition>
                   <ColumnDefinition></ColumnDefinition>
             </Grid.ColumnDefinitions>
                    <Label VerticalAlignment="Center" FontWeight="Bold" Content="Name:" Grid.Row="0" Grid.Column="0"></Label>
                    <Label VerticalAlignment="Center" FontWeight="Bold" Grid.Row="1" Grid.Column="0" Content="LastName:"></Label>
                    <Label VerticalAlignment="Center" FontWeight="Bold" Grid.Row="2" Grid.Column="0" Content="Age:"></Label>
                    <TextBlock  VerticalAlignment="Center" Grid.Row="0" Grid.Column="1" x:Name="Name" Width="120" Text="{Binding Name}" ></TextBlock>
                    <TextBlock  VerticalAlignment="Center" Grid.Row="1" Grid.Column="1" x:Name="LastName" Width="120" Text="{Binding LastName}" ></TextBlock>
                    <TextBox  VerticalAlignment="Center" Grid.Row="2" Grid.Column="1" x:Name="Age" Width="120" Text="{Binding Age}" ></TextBox>

                </Grid>
            </StackPanel>
        </DataTemplate>

      </Window.Resources>
      <Grid>
      <StackPanel>
        <ListBox x:Name="LstModels" Margin="25" ItemsSource="{Binding 
          Models}" ItemTemplate="{Binding Source={StaticResource 
           ModelItemTemplate}}"></ListBox>
           <Button Width="120" Height="40" Click="AddModel_OnClick" 
           Content="Add Model" ></Button>
       </StackPanel>
       </Grid>
       </Window>

And Code-behind is...

    public partial class MainWindow : Window
     {
        public ObservableCollection<Model> Models{ get; set; }
          public MainWindow()
          {
              InitializeComponent();
              Models= new ObservableCollection<Model> { new Model{ Name = 
             "Shabana", LastName = "Parveen", Age = 35, PicturePath = 
               @"Images\pic.bmp" },
             new Model { Name = "Ada", LastName = "Lovelace", Age = 37, 
             PicturePath = @"Images\AdaLovelace.bmp" }};

           // this.DataContext = this;

          }
     }
Shabana
  • 69
  • 6

2 Answers2

3

The behaviour you are observing is due to the order in which you Initialize the View and instantiate the ObservableCollection.

When you assign the DataContext in XAML, all your XAML is parsed when you call InitilizeComponent() in the constructor. The issue is: at the time that your XAML is being parsed, Models is still null as it is instantiated AFTER you call InitilizeComponent(). For this reason, all bindings fail as the collection you're binding to is null when the Binding are evaluated (which happens as xaml is being parsed). When you do instantiate Models, the View is not aware of this, as you have not implemented any notification mechanism.

On the other hand, when you assign the DataContext in the code behind, you do it AFTER all the XAML has been parsed, which forces the View to evaluate the Bindings at the time of assignment...thus it works.

There at at least 3 solutions to this I can think of:

  1. Change the order of initialization

As Andy suggested in his answer, if you first instantiate the Models collection and then call InitilizeComponent(), it will work. As XAML is being parsed, the View will have a non-null DataContext, so all data will propagate to your View via Bindings.

  1. Implement the INotifyPropertyChanged interface.

What this will do is allow you to instantiate Models whenever it is convenient, then Raise the PropertyChanged event to notify the view. This will work just as well. But if you're going to this much trouble, you should really be looking at using the MVVM pattern (lots of tutorials online). I will leave the details of implementation to you as there are countless examples and tutorials out there. Here are a few you can start with:

From SO, nice and to the point

More elaborate explanation here

From WPF Tutorial.net

  1. Make Models a Dependency Property.

If you insist on doing this in the code behind (rather than MVVM), DependencyPropeties will work well. Dependency Properties have their own notification mechanism. Despite being very verbose, they are quite easy to implement. Below is a good source, but you can find lots by just Googling it.

See this article

Nik
  • 1,780
  • 1
  • 14
  • 23
  • 1
    Thank you Nik, your explanation helps a lot. Cheers – Shabana Apr 15 '18 at 04:52
  • Nik, your answer really helps me to understand as what is going on under-hood. I am planning to implement MVVM later. Right now trying to understand the concepts step by step. Cheers – Shabana Apr 15 '18 at 05:09
2

Your problem is because you have a null observablecollection. If you move your code round, it'll work:

public partial class MainWindow : Window
 {
    public ObservableCollection<Model> Models{ get; set; }
      public MainWindow()
      {

          Models= new ObservableCollection<Model> { new Model{ Name = 
         "Shabana", LastName = "Parveen", Age = 35, PicturePath = 
           @"Images\pic.bmp" },
         new Model { Name = "Ada", LastName = "Lovelace", Age = 37, 
         PicturePath = @"Images\AdaLovelace.bmp" }};

          InitializeComponent();

      }
 }

You should look into using a separate class as a viewmodel and MVVM generally.

Andy
  • 11,864
  • 2
  • 17
  • 20