Firstly a Disclaimer: I have read dozens of posts with hundreds of answers and yet I have not found a solution. I have attempted to resolve this every which way I can find and yet the UI does not update.
I am writing a UWP app as a personal project. In this particular scenario I have a Grid with an assigned DataContext with a series of TextBlocks which are Bound to Properties. I am using "Binding" and not "x:Bind" due to the Grid been part of a ControlTemplate.
The issue: The UI completely ignores the changes and NEVER updates.
I have set the properties with a value within the Page Constructor, and that is set, but no changes thereafter are applied... EVER.
Please see the relevant code snippets below and maybe someone can see something I have not.
Thanks in advance.
Grid XAML:
<Grid
Grid.Row="1"
BorderBrush="{StaticResource SystemControlChromeLowAcrylicElementBrush}"
BorderThickness="2,0,2,2">
<Grid.DataContext>
<views:MenusPage />
</Grid.DataContext>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="152" />
<ColumnDefinition Width="152" />
<ColumnDefinition Width="152" />
<ColumnDefinition Width="152" />
<ColumnDefinition Width="152" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="3"
Padding="15,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontWeight="Bold"
Text="Totals" />
<Border
Grid.Row="0"
Grid.Column="3"
Margin="0,2,0,-2"
BorderBrush="{StaticResource SystemControlChromeLowAcrylicElementBrush}"
BorderThickness="2,0,0,0">
<TextBlock
Padding="10,0,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding CaloriesTotal, Mode=OneWay}" />
</Border>
<Border
Grid.Row="0"
Grid.Column="4"
Margin="0,2,0,-2"
BorderBrush="{StaticResource SystemControlChromeLowAcrylicElementBrush}"
BorderThickness="2,0,0,0">
<TextBlock
Padding="10,0,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding ProteinTotal}" />
</Border>
<Border
Grid.Row="0"
Grid.Column="5"
Margin="0,2,0,-2"
BorderBrush="{StaticResource SystemControlChromeLowAcrylicElementBrush}"
BorderThickness="2,0,0,0">
<TextBlock
Padding="10,0,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding FatTotal}" />
</Border>
<Border
Grid.Row="0"
Grid.Column="6"
Margin="0,2,0,-2"
BorderBrush="{StaticResource SystemControlChromeLowAcrylicElementBrush}"
BorderThickness="2,0,0,0">
<TextBlock
Padding="10,0,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding CarbTotal}" />
</Border>
<Border
Grid.Row="0"
Grid.Column="7"
Margin="0,2,0,-2"
BorderBrush="{StaticResource SystemControlChromeLowAcrylicElementBrush}"
BorderThickness="2,0,0,0">
<TextBlock
Padding="15,0,0,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding SugarTotal}" />
</Border>
</Grid>
Properties:
private double _caloriesTotal;
public double CaloriesTotal
{
get { return _caloriesTotal; }
set { Set(ref _caloriesTotal, value); }
}
private double _proteinTotal;
public double ProteinTotal
{
get { return _proteinTotal; }
set { Set(ref _proteinTotal, value); }
}
private double _fatTotal;
public double FatTotal
{
get { return _fatTotal; }
set { Set(ref _fatTotal, value); }
}
private double _carbTotal;
public double CarbTotal
{
get { return _carbTotal; }
set { Set(ref _carbTotal, value); }
}
private double _sugarTotal;
public double SugarTotal
{
get { return _sugarTotal; }
set { Set(ref _sugarTotal, value); }
}
PropertyChanged:
public event PropertyChangedEventHandler PropertyChanged;
private void Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
{
return;
}
storage = value;
OnPropertyChanged(propertyName);
}
private void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Code Setting the values: (NOTE: this is the shortened version but the same effect applies, setting the values in the constructor applies the values and UI updates but setting the values anywhere else on the page, within any method that is executed, they are ignored and the UI shows zeros.)
CaloriesTotal = 10;
ProteinTotal = 20;
FatTotal = 30;
CarbTotal = 40;
SugarTotal = 50;
I found the solution, and while I am dumbfounded as to how it actually worked while the other 'normal' solutions that should have worked did not, is beyond me.
Anyway thanks to @jsmyth886 and @Alexey for your help and suggestions in troubleshooting.
Since posting this question I have tried separating out the properties into another class, referencing <Models:MenuTypesModel x:Key="MenuTotals" />
within the <Page.Resources>
block -- failed
I Tried setting the DataContext
of the Page
itself -- failed
The <Grid> ... </Grid>
XAML in my question is, as said, part of a ControlTemplate
I even moved the block out of the Control Template and onto the Page itself to eliminate any clashing with other datasources - mind you all different naming structures and paths, so ultimately should not have clashed -- this did not work either, so -- failed
It was @Alexey response that got me thinking in a different line and my searching ultimately led me to this post Binding to Self/'this' in XAML which solved my problem.
I removed the following datacontext from the Grid:
<Grid.DataContext>
<views:MenusPage />
</Grid.DataContext>
And replaced it with this DataContext="{Binding ElementName=_this}"
and made sure of my textblock bindings, the end result (Very Short Version):
<Grid ... DataContext="{Binding ElementName=_this}">
...
<TextBlock ... Text="{Binding CaloriesTotal}" />
...
</Grid>
And finally added the a Name attribute to the Page itself:
x:Name="_this"
And it worked!!!!
I have changes nothing in the way I have set my properties.
My properties are still defined as per the question, I have no viewmodels or additional classes, nor am I setting DataContext in the CodeBehind.
Simply adding DataContext="{Binding ElementName=_this}"
the grid and x:Name="_this"
to the Page allows the UI to reflect the changes to the properties every time.
An oddly simple solution.
A NOTE: though, while I was trying different things though, when I had the Grid on the page itself outside of the ContentTemplate, I set the DataContext of the page in codebehind DataContext = this;
and it started populating but still failed to affect the Grid in the Control template.
So unless there is a clash that I didn't see or because I have other DataSources which are unrelated as well as assigned directly the to control that needs it... I cannot figure why it works now with this simple binding.
Anyway, thanks for the assistance and I hope this helps someone else in the future.