dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.07k stars 1.17k forks source link

DynamicResource is not working for Header property in DataGridTextColumn #6302

Open vijayarasan opened 2 years ago

vijayarasan commented 2 years ago

I have defined the DynamicResource for Header property in DataGridTextColumn of DataGrid. Please refer the below code snippet,

<Application.Resources>
        <System:String x:Key="firstName"  >Before Change Name</System:String>
        <System:String x:Key="secondName" >After Change Name</System:String>
    </Application.Resources>
<DataGridTextColumn Binding="{Binding CustomerID}" 
                                    Header="{DynamicResource firstName}" />

When change the resource at runtime Header property does not change the value in DataGrid. Can you please check and elaborate why DynamicResource is not working in DataGridTextColumn.Header property of DataGrid?

Note : If we use HeaderTemplate property in DataGridTextColumn its working properly in DataGrid. Please refer the code snippet,

<Window.Resources>
        <DataTemplate x:Key="headerTemplate">
            <TextBlock Height="50"                    
                    Text="{DynamicResource firstName}"
                    TextWrapping="Wrap" />
        </DataTemplate>
</Window.Resources>   

<Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="200" />
        </Grid.ColumnDefinitions>
        <DataGrid x:Name="dataGrid"                                 
                  ItemsSource="{Binding Orders}"
                  AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding OrderID}" 
                                    HeaderTemplate="{StaticResource headerTemplate}" />
                <DataGridTextColumn Binding="{Binding CustomerID}" 
                                    Header="{DynamicResource firstName}" />
                <DataGridTextColumn Binding="{Binding CustomerName}" Header="Customer Name" />
                <DataGridTextColumn Binding="{Binding Country}" Header="Country" />
                <DataGridTextColumn Binding="{Binding UnitPrice}" Header="Unit Price" />
            </DataGrid.Columns>
        </DataGrid>
</Grid>

Please refer the Screenshot for your reference

Resource

Minimal repro: Step 1: Run the sample

Step 2: Click the Change resource button

Actual behavior: DynamicResource is not working for Header property in DataGridTextColumn

Expected behavior: DynamicResource is working properly for Header property in DataGridTextColumn

I have attached the sample for your reference. Sample Link: DataGrid.zip

Can you please check and provide the solution to use the DynamicResource in Header property in DataGridTextColumn?

Regards, Vijayarasan S

miloush commented 2 years ago

The DataGridColumns are only descriptors, based on which the actual elements are created. The column headers are presented by DataGridColumnHeadersPresenter, an ItemsControl using DataGridColumnHeader as a container. When DataGridColumnHeader container is created, the property values are transferred onto it from DataGridColumn, e.g. the DataGridColumn.Header property onto DataGridColumnHeader.Content property (similar with templates):

https://github.com/dotnet/wpf/blob/b63c69eaf5b58e758765a37bb18064bbc94832ad/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Controls/Primitives/DataGridRowHeader.cs#L326-L331

Notably, it's property values, not the DP expressions that are copied over.


Now the DynamicResource resp. ResourceReferenceExpression needs something that can provide the resources. It looks for an inheritance context of the target DependencyObject. However, DataGridColumn not only is not in the visual tree as discussed above, it inherits directly from DependencyObject and does not implement any inheritance context.

There is a special provision in the ResourceReferenceExpression for the cases where the target object does not have any inheritance context, which fallbacks to app and/or system resources. In the example above, this is where the initial value comes from:

https://github.com/dotnet/wpf/blob/89d172db0b7a192de720c6cfba5e28a1e7d46123/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/ResourceReferenceExpression.cs#L150-L151


Finally, when resource is changed, the owner of the ResourceDictionary is notified. For Framework[Content]Element owners, the ResourcesChanged event is raised. For app resources, the event is propagated through the tree in app's windows:

https://github.com/dotnet/wpf/blob/89d172db0b7a192de720c6cfba5e28a1e7d46123/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/ResourceDictionary.cs#L1690

If ResourcesReferenceExpression can get hold of inheritance context in the form of Framework[Content]Element, it can simply subscribe to the ResourcesChanged event and propagate the changes to the target DO. Any changes to the app resources will come to it through the visual tree.

As we noted above, in this scenario we have neither inheritance context nor visual tree. The app/system resources have no other means of notifying of changes, so the ResourceReferenceExpression does not have any way to subscribe to the changes in this case.


I believe that an easy way to hotfix this issue would be to for the DataGridColumn to return DataGridOwner as its InheritanceContext. (That sounds like a reasonable idea anyway, unless I missed some undesirable consequences.)

However, that would not fix the issue for other types in this situation (including user types). It seems that Application and perhaps even SystemResources should have an event that the ResourceReferenceExpression could subscribe to to learn about changes to these resources.

anjali-wpf commented 2 years ago

@vijayarasan

For this one you are mixing two things together, you have to pick either of the ones mentioned below.

Either use

<Window.Resources>
        <DataTemplate x:Key="headerTemplate">
            <TextBlock Height="50"                    
                    Text="{DynamicResource firstName}"
                    TextWrapping="Wrap" />
        </DataTemplate>
    </Window.Resources>  

and

<DataGridTextColumn Binding="{Binding OrderID}" 
                                    HeaderTemplate="{StaticResource headerTemplate}" />

Or use

<Window.Resources>
        <TextBlock x:Key="TextBlockName" Height="50"                    
                    Text="{DynamicResource firstName}"
                    TextWrapping="Wrap" />
    </Window.Resources> 

and

<DataGridTextColumn Binding="{Binding OrderID}" 
                                    Header="{StaticResource TextBlockName}" />

In this first approach we are setting style with the help of header template, and in the second one we are setting it with the help of static resource.

I hope this addresses your question, in such a case we can close it.

miloush commented 2 years ago

@anjalisheel-wpf this does not address the fact that the DynamicResource extension does not work on DataGridColumn.

vijayarasan commented 2 years ago

Hi @anjalisheel-wpf,

Sorry for the delay,

The provided workaround does not satisfy our requirements. Our need is to achieve this requirement in a simple way like the below,


HeaderText={DynamicResource something}.
vijayarasan commented 2 years ago

Hi @miloush,

Sorry for the delay.

Seems the mentioned PR is still in progress. When we expect the fix for the reported issue

miloush commented 2 years ago

Why is "simple way" a requirement? I am not doing any more work on the PR, I was looking for more feedback on whether that is a reasonable way to support this scenario.

vijayarasan commented 2 years ago

Hi @miloush,

The purpose of requesting the simple way to access the property that is bound for HeaderText is a very simple comparatively process with HeaderTemplate