首页 > 解决方案 > 如何在 ResourceDictionary / 模板文档中通过 Trigger/Setter 更新值时保持双向绑定

问题描述

        <Grid x:Name="RedGrid" Visibility="{Binding Path=IsVisible,
        Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">

如您所见,此 Grid 的 Visibility 与 IsVisible 属性相关联。

        <DataTemplate.Triggers>
            <Trigger SourceName="RedGrid" Property="Opacity" Value="0">
                <Setter TargetName="RedGrid" Property="Visibility" Value="Collapsed"/>     
            </Trigger>
        </DataTemplate.Triggers>

一旦不透明度达到/等于零,这部分代码将改变 RedGrid 的可见性

但显然第二部分代码不起作用,因为当 RedGrid 的 Visibility 成功更改为 Collapsed 时,不会调用 IsVisible 的 Setter。

    public Visibility IsVisible
    {
        get => _isVisible;

        set
        {
           _isVisible = value;
           RaisePropertyChanged("IsVisible");
        }
    }

标签: c#xamldata-binding

解决方案


What you are trying to do is to update the Visibility property of the Grid using a Setter and maintain the binding at the same time. The problem is that binding is overwritten by the Setter!

When you use Xaml Setters you set properties the same way as you set them in the tag, so when you write:

<Setter TargetName="RedGrid" Property="Visibility" Value="Collapsed"/>

It acts the same way as if you've written:

<Grid x:Name="RedGrid" Visibility="Collapsed">

Notice that the binding Visibility="{Binding Path=IsVisible"} has been overwritten by the value Visibility="Collapsed" and will no longer work.


But Why?

The way that TwoWay binding works can be found inside the controls themselves. When you work with DependencyProperties by default you set them using SetValue() method, which sets the property's value and overwrites any binding in place.

However, in controls like TextBox or CheckBox that take in user input, when the user interacts with them, properties like TextBox.Text and CheckBox.IsChecked are being set by the method SetCurrentValue() which like the documentation says:

The SetCurrentValue method changes the effective value of the property, but existing triggers, data bindings, and styles will continue to work.

You can review this question for more information about the differences between SetValue() and SetCurrentVaule() methods.


What to do:

In your case, I think that you can add a GridOpacity property to the view model, and instead of changing the Visibility in Xaml using a Setter, you can change the view model's IsVisible property directly when you set the GridOpacity property.

Here is a sample code of the view model:

private Visibility _isVisible;
public Visibility IsVisible
{
    get => _isVisible;
    set
    {
        if (_isVisible == value)
        {
            return;
        }
        _isVisible = value;
        RaisePropertyChanged(nameof(IsVisible));
    }
}

private double _gridOpacity;
public double GridOpacity
{
    get => _gridOpacity;
    set
    {
        if (_gridOpacity == value)
        {
            return;
        }
        _gridOpacity = value;
        RaisePropertyChanged(nameof(GridOpacity));

        // Change the visibility of the grid when opacity hits 0.
        if (value == 0)
        {
            IsVisible = Visibility.Collapsed;
        }
        else
        {
            // Change it back.
            IsVisible = Visibility.Visible;
        }
    }
}

And here is the Xaml:

<Grid x:Name="RedGrid" Visibility="{Binding IsVisible}" Opacity="{Binding GridOpacity}">
    ...
</Grid>


推荐阅读