Sergio Di Fiore
Sergio Di Fiore

Reputation: 466

WPF: Frame to receive data within a ItemsControl

I need a part of my Page unmovable, so I thought using a Frame, something like:

<StackPanel>
     <Label Style="{StaticResource OneThirdColumnLabel}">Administrar papéis: Alterar papéis</Label>
         <ItemsControl x:Name="PapeisIc">
             <ItemsControl.ItemTemplate>
                 <DataTemplate>
                      <Frame x:Name="_papeisAddFrame" Source="PapeisAdd.xaml" Height="50"/>
                  </DataTemplate>
              </ItemsControl.ItemTemplate>
          </ItemsControl>
</StackPanel>

Then, the Frame would contain a Page with:

<ScrollViewer VerticalScrollBarVisibility="Visible">
    <StackPanel>
        <StackPanel Orientation="Horizontal" Margin="0,0,0,10">
            <Label Style="{StaticResource LaterLabel}">Papel:</Label>
            <TextBox Text="{Binding Papel}" Style="{StaticResource StdTextBox}" Width="100" />
            <Label Style="{StaticResource LaterLabel}">Descrição:</Label>
            <TextBox Text="{Binding Descricao}" Style="{StaticResource StdTextBox}" Width="400" />
            <Button x:Name="DeleteFieldBtn" Style="{StaticResource deleteFieldButtom}" ToolTip="Eliminar este papel"/>
        </StackPanel>
    </StackPanel>
</ScrollViewer>

In the code-behind, the line:

PapeisIc.ItemsSource = _papeis;

Binds a object that's an ObservableCollection of 3 items and I need it to display the 3 elements in the example.

What I get is:

enter image description here

So that, somehow it knows that there are 3 elements, but it doesn't bind it right...

I already tried to pass to the Page the data as a parameter, but it didn't work either.

How can I bind those values?

Just to make it clear, I'm trying to bind to an ObservableCollection of:

public class Role
{
    public int Id { get; set; }
    public string Papel { get; set; }
    public string Descricao { get; set; }
}

Upvotes: 0

Views: 266

Answers (1)

thatguy
thatguy

Reputation: 22079

The Frame control does not pass the DataContext down to the Page. The easiest way to solve this issue is to remove the Frame and move its content directly to the DataTemplate.

If you really need the Frame, you have to listen to the LoadCompleted and DataContextChanged events and propagate the DataContext manually to the content of the Frame. I show you how to do it with a TriggerAction using the Microsoft.Xaml.Behaviors.Wpf NuGet package.

public class UpdateContentDataContextAction : TriggerAction<Frame>
{
   protected override void Invoke(object parameter)
   {
      if (AssociatedObject.Content is FrameworkElement frameworkElement)
         frameworkElement.DataContext = AssociatedObject.DataContext;
   }
}
<DataTemplate>
   <Frame x:Name="_papeisAddFrame" Source="PapeisAdd.xaml" Height="50">
      <b:Interaction.Triggers>
         <b:EventTrigger EventName="LoadCompleted">
            <local:UpdateContentDataContextAction/>
         </b:EventTrigger>
         <b:EventTrigger EventName="DataContextChanged">
            <local:UpdateContentDataContextAction/>
         </b:EventTrigger>
      </b:Interaction.Triggers>
   </Frame>
</DataTemplate>

If you want to do it in the code-behind, subscribe the events on the control and use the code of the trigger action.

<Frame x:Name="_papeisAddFrame" Source="PapeisAdd.xaml" Height="50" LoadCompleted="OnLoadCompleted" DataContextChanged="OnDataContextChanged">
private void OnLoadCompleted(object sender, NavigationEventArgs e)
{
   PropagateDataContext((Frame)sender);
}

private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
   PropagateDataContext((Frame)sender);
}

private void PropagateDataContext(Frame frame)
{
   if (frame.Content is FrameworkElement frameworkElement)
      frameworkElement.DataContext = frame.DataContext;
}

Upvotes: 1

Related Questions