wpf: After user click the Expander header, IsExpanded not work by trigger.

  • .NET Core Version:
.NET SDK (反映任何 global.json):
 Version:   5.0.201
 Commit:    a09bd5c86c

运行时环境:
 OS Name:     Windows
 OS Version:  10.0.19042
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\5.0.201\

Host (useful for support):
  Version: 5.0.4
  Commit:  f27d337295

.NET SDKs installed:
  2.2.207 [C:\Program Files\dotnet\sdk]
  5.0.201 [C:\Program Files\dotnet\sdk]
  • Windows version: Win10 20H2
  • Does the bug reproduce also in WPF for .NET Framework 4.8?: I don’t know.
  • Is this bug related specifically to tooling in Visual Studio (e.g. XAML Designer, Code editing, etc…)? If yes, please file the issue via the instructions here.
  • Security issues and bugs should be reported privately, learn more via our responsible disclosure guidelines.

Problem description: I want to expand the Expander when the TextBox has validation error. But after I click the Expander header by mouse once, the IsExpanded property never works.

image

Let’s see, its background is changed, so the trigger is no problem.

image

Actual behavior: After user click the Expander header, Expander.IsExpanded no longer expand the content.

Expected behavior: After user click the Expander header, Expander.IsExpanded should expand the content when it is True.

Minimal repro:

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 15 (7 by maintainers)

Most upvoted comments

@GF-Huang - I can explain why this is happening, but I don’t see how to achieve the effect you want. I think it needs a new feature.

The problem is an unfortunate interaction between your declaration

    <Expander  IsExpanded="{Binding (Validation.HasError), ElementName=TextBox, Mode=OneWay}">

and a declaration for the Expander’s ToggleButton (from the theme style):

      IsChecked="{Binding Path=IsExpanded,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"

When you click the button, the second binding writes a new value into the expander, essentially calling expander.SetValue(IsExpandedProperty, true). The first time this happens, it overwrites the first binding, which goes away never to return. You can see this using the VS Live Property Explorer: look at the expander’s IsExpanded property in the “Local” section and be prepared to refresh or re-select the expander (the explorer in my version of VS is not as “live” as it should be); before clicking it shows a BindingExpression, after clicking it shows a raw boolean value.

This overwriting wouldn’t happen if your binding were two-way, or if the second binding used SetCurrentValue instead of SetValue. But I don’t see how to use a two-way binding to get your effect, and there’s no way to tell the second binding to use SetCurrentValue. And I haven’t found any other tricks to work around this problem (replacing the second binding, using intermediate properties, intercepting the SetValue call, etc.). Maybe someone in the community is up to the challenge?

Feature request: A setting on Binding that tells it to use SetCurrentValue to write values, if possible. Update Expander’s theme style to use the setting. Locate and update all similar uses.

Alternatively: Breaking change to make Binding always use SetCurrentValue. Of course, only if a deep study proves that it’s always the right thing to do.

History: WPF controls used to use SetValue to change their own properties, which caused hundreds of bugs similar to yours because SetValue overwrites one-way bindings and hides values from styles, triggers, and templates. We introduced SetCurrentValue in .NET 4.0 to fix this, but didn’t consider the indirect case of a two-way binding doing the change. And yours is the first complaint I’m aware of in the 12 years since then. So it’s apparently not a common scenario.

Ran into the same problem, but could replace the Expander with a “selfmade” Expander that is made up of a ToggleButton and content area. The binding with the isChecked Property of the ToggleButton does work as expected unlike the Expanders property.

<StackPanel>
        <Button Command="{Binding ResetCommand}">RESET PROPERTY</Button>
        <Button Content="CloseAll"
                      Command="{Binding CloseAllCategoriesCommand}"></Button>
        <Button Content="ShowAll"
                      Command="{Binding ShowAllCategoriesCommand}"></Button>
        <ItemsControl x:Name="_ItemsControl" Margin="0,20,0,0">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <!--<Expander Header="{Binding Name}"
                              IsExpanded="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}, Path=DataContext.ShowAll, Mode=OneWay}">
                        <Border Width="500" Height="300" Background="OrangeRed"></Border>
                    </Expander>-->
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <ToggleButton x:Name="ToggleButton"
                                      Content="{Binding Name}" Background="AliceBlue"
                                      IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}, Path=DataContext.ShowAll, Mode=OneWay}" Margin="0,5"></ToggleButton>
                        <Border  Grid.Row="1"
                                 Width="500"
                                Height="150"
                                 Visibility="{Binding ElementName=ToggleButton, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}"
                                Background="OrangeRed"></Border>
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>

@lindexi - no, not today. [I would have mentioned it if there were.]