BUG: WinUI3 x:Bind in GridView
See original GitHub issueDescribe the bug Using {x:Bind} on a DependencyPropery inside a Control which is used in the ItemTemplate of a GridView doesn’t work. It shows the FallBackValue and not the correct property. If you change the ItemsPanel of the GridView to a StackPanel it works ok. Normal {Binding} also works ok.
In the following example I have two GridViews. One without an ItemsPanel, the second one with a StackPanel. The GridViews ItemSource is databound to a List of Employee (Name/Salary) objects. The ItemTemplate contains a UserControl (EmployeeControl) which has an Employee DependencyProperty. This property is set using x:Bind.
<Window
x:Class="ProblemDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ProblemDemo"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Padding="8">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<GridView ItemsSource="{x:Bind Employees}">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:Employee">
<local:EmployeesControl Background="Red"
Employee="{x:Bind}" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<GridView ItemsSource="{x:Bind Employees}"
Grid.Row="1">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:Employee">
<local:EmployeesControl Background="Blue"
Employee="{x:Bind}" />
</DataTemplate>
</GridView.ItemTemplate>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
</Grid>
</Window>
using Microsoft.UI.Xaml;
using System.Collections.Generic;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace ProblemDemo {
/// <summary>
/// An empty window that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainWindow : Window {
internal List<Employee> Employees = new() {
new("Fons", 2000),
new("Ellen", 5000),
new("Jim", 3000),
};
public MainWindow() {
this.InitializeComponent();
}
}
public class Employee {
public Employee(string name, double salary) {
this.Name = name;
this.Salary = salary;
}
public string Name { get; set; }
public double Salary { get; set; }
}
}
The EmployeeControl has a StackPanel with 2 TextBlock controls in it. The TextBlock Text is databound using x:Bind to the Name and Salary. It also has a ‘dummy’ FallbackValue.
<UserControl x:Class="ProblemDemo.EmployeesControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ProblemDemo"
RequestedTheme="Dark"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel Background="{x:Bind Background}"
Spacing="8"
Padding="8">
<TextBlock Text="{x:Bind Employee.Name, FallbackValue=NameFallBack}"
FontWeight="Bold" />
<TextBlock Text="{x:Bind Employee.Salary, FallbackValue=0}" />
</StackPanel>
</UserControl>
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
namespace ProblemDemo {
public sealed partial class EmployeesControl : UserControl {
public EmployeesControl() {
this.InitializeComponent();
}
public Employee Employee {
get => (Employee)GetValue(EmployeeProperty);
set => SetValue(EmployeeProperty, value);
}
public static readonly DependencyProperty EmployeeProperty = DependencyProperty.Register(nameof(Employee), typeof(Employee), typeof(EmployeesControl), new PropertyMetadata(default));
}
}
When you run the code you will see that the first GridView (Red) is showing the FallbackValue. This is the BUG. The second one (Blue) works ok. This is becuase it has the StackPanel in the ItemsPanel. It will stop working you change it to an ItemsStackPanel or ItemsWrapGrid.
Steps to reproduce the bug
Steps to reproduce the behavior:
- Clone the repository.
- Open the Solution
- Run the app
Expected behavior The Red StackPanels should show the correct Name and Salary of all employees. Just like the blue ones do.
Screenshots
Version Info
NuGet package version: [WinUI 3 - Windows App SDK 0.8: 0.8.2] [WinUI 3 - Windows App SDK 1.0: 1.0.0-experimental1] [WinUI 3 - Windows App SDK 1.0: 1.0.0-preview1]
Windows app type:
UWP | Win32 |
---|---|
Yes |
Windows version | Saw the problem? |
---|---|
Insider Build (xxxxx) | |
May 2021 Update (19043) | Yes |
October 2020 Update (19042) | |
May 2020 Update (19041) | |
November 2019 Update (18363) | |
May 2019 Update (18362) | |
October 2018 Update (17763) | |
April 2018 Update (17134) | |
Fall Creators Update (16299) | |
Creators Update (15063) |
Device form factor | Saw the problem? |
---|---|
Desktop | Yes |
Xbox | |
Surface Hub | |
IoT |
Additional context
Issue Analytics
- State:
- Created 2 years ago
- Comments:9 (6 by maintainers)
Top GitHub Comments
This looks like a bug with
ItemsStackPanel
, causing x:Binds to be evaluated in an incorrect order. As a workaround, the x:Binds inEmployeeControls.xaml
should useMode=OneWay
.x:Bind’s logic is initially evaluated when the content in its scope is fully initialized. When x:Bind is used with data root of a control or page, it uses the root’s
FrameworkElement.Loading
to determine when to run. When x:Bind is used in a template, it instead uses the template root’sFrameworkElement.DataContextChanged
event, or the framework can directly run the x:Bind logic by invokingIDataTemplateComponent.ProcessBindings
orIDataTemplateTemplateExtensions.ProcessBindings
.In the working case with non-
ItemsStackPanel
roots inGridView.ItemsPanel
, the events looks like this:GridView
’s template instances instantiate theEmployeeControl
control and sets itsBackground
propertyGridView
’s template instance runs as a result ofDataContextChanged
firing, setting theEmployeeControl.Employee
propertyEmployeeControl
’s x:Bind logic (setting itsTextBlock
s text to Employee fields) runs as a result of aLoading
eventIn the non-working case with
ItemsStackPanel
as theGridView.ItemsPanel
(which is its default value):GridView
’s template instances instantiate theEmployeeControl
control and sets itsBackground
propertyEmployeeControl
’s x:Bind logic (setting itsTextBlock
s text to Employee fields) runs as a result of aLoading
event. However,EmployeeControl.Employee
hasn’t been set yet, so the x:Binds evaluate to incorrect values.GridView
’s template instance runs as a result of aProcessBindings
call from the framework, setting theEmployeeControl.Employee
propertyI made a repro here (link will only work for MSFT folks). @stephenlpeters can you redirect to someone on the controls team who is more familiar with
ItemsStackPanel
? It may have something to do with whenItemsStackPanel
callsMeasure
for its template instances (Measure
is what would trigger theFrameworkElement.Loading
event and cause x:Bind’s logic to run).This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 5 days.