【问题标题】:WPF Converter not running when binding to the whole object and some properties has changed绑定到整个对象并且某些属性已更改时,WPF 转换器未运行
【发布时间】:2020-06-18 11:36:34
【问题描述】:

我已经达到了我的心理障碍,但无法弄清楚。我确定我错过了一些简单的东西,但我被卡住了。下面的代码是查看我的问题所需的最少代码,但与我的生产代码相去甚远。

设置:
我有一个带有 DataGrid 控件的 WPF 窗口,该控件绑定到包含 资产集合 的业务对象。对于每个资产,我需要显示一个用户控件(SpecialButton),它的可见性是根据资产对象的多个属性确定的。当我单击按钮时(在我的示例中,为了简单起见,我有一个额外的按钮可以更改属性),它会更改基础资产对象的属性,该属性应该使控件隐藏。

问题
我将用户控件附加属性ControlVisibility 绑定到整个资产对象{Binding .}
<local:SpecialButton x:Name="buttonOnEachRow" ControlId="{Binding Id}" ControlVisibility="{Binding ., Converter={StaticResource MyConverter}}"/>
当我更改资产对象 PropertyA 的属性时,我希望 MyConverter 应该运行并更改可见性值,但它永远不会发生。

我的尝试
我尝试了很多我什至不记得的事情。最有希望的似乎是MultipleBinding,但我无法弄清楚如何为ControlVisibility 属性编写语法。我在DataGrid 控件上尝试了一些设置,改变了user control 的更新方式,但没有成功。
作为一种解决方法,在我的生产代码中,我创建了一个 fake 属性,该属性执行当前转换器中的逻辑并将ControlVisibility 绑定到fake 属性。这可行,但我的资产对象中有一个完全不相关的属性,只是因为我无法弄清楚绑定。

WPF 主窗口

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;

namespace MultiBindingProblem
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();

            var sut = new BusinessObject() { Caption = "This is the parent object", Assets = new List<Asset>()};
            sut.Assets.Add(new Asset() { Name = "Asset 1", Id = 1 });
            sut.Assets.Add(new Asset() { Name = "Asset 2", Id = 2 });
            sut.Assets.Add(new Asset() { Name = "Asset 3", Id = 3 });
            this.DataContext = sut;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void BtnCancel_Click(object sender, RoutedEventArgs e)
        {
            Close();
        }

        private void BtnChange_Click(object sender, RoutedEventArgs e)
        {
            ((BusinessObject)this.DataContext).Assets[0].PropertyA = true;
            //PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Assets"));            
        }
    }
}

XAML btnChange 在这里是为了简单起见。在我的生产代码中,SpecialButton 将触发我的视图模型中的属性更新

<Window x:Class="MultiBindingProblem.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:MultiBindingProblem"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800" WindowStartupLocation="CenterScreen">

        <Window.Resources>
            <local:TestConverter x:Key="MyConverter" />
        </Window.Resources>

        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="0.5*" />
                <ColumnDefinition Width="0.5*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="50" />
                <RowDefinition Height="*" />
                <RowDefinition Height="100" />
            </Grid.RowDefinitions>

            <TextBlock x:Name="lblMainObject" Grid.ColumnSpan="2" Grid.Row="0" FontSize="25"
                       Text="{Binding Caption}" />
            <Button x:Name="btnCancel" Content="Cancel" IsCancel="True" IsDefault="True" Grid.Row="2" Grid.Column="1" Click="BtnCancel_Click" />
            <DataGrid x:Name="dgrData" Grid.Row="1" Grid.ColumnSpan="2" AutoGenerateColumns="False" CanUserAddRows="False"
                      ItemsSource="{Binding Assets, NotifyOnSourceUpdated=True}" >
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
                    <DataGridTemplateColumn Header="Action button" Width="100">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <!--
                                Here I bind to the whole 'Asset' object to be able to determine if the button should be
                                visible based on multiple properties. But changing a propety doesn't raise the converter.
                                I tried use multiple bindings but I was not able to figure out the syntax
                                -->
                                <local:SpecialButton x:Name="buttonOnEachRow" 
                                                     ControlId="{Binding Id}"
                                                     ControlVisibility="{Binding ., Converter={StaticResource MyConverter}}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </DataGrid>

            <Button x:Name="btnChange" Grid.Row="2" Grid.Column="0" Content="Change visibility of the first button" Click="BtnChange_Click" />

        </Grid>
    </Window>

用户控件(特殊按钮)

using System.Windows;
using System.Windows.Controls;

namespace MultiBindingProblem
{
    public partial class SpecialButton : UserControl
    {
        public SpecialButton()
        {
            InitializeComponent();
        }

        public static readonly DependencyProperty ControlIdProperty =
                                DependencyProperty.Register("ControlId", typeof(int),
                                typeof(SpecialButton));

        public int ControlId
        {
            get { return (int)GetValue(ControlIdProperty); }
            set { SetValue(ControlIdProperty, value); }
        }

        public static readonly DependencyProperty ControlVisibilityProperty =
                                DependencyProperty.Register("ControlVisibility", typeof(Visibility),
                                typeof(SpecialButton), new FrameworkPropertyMetadata(Visibility.Visible));

        public Visibility ControlVisibility
        {
            get { return (Visibility)GetValue(ControlVisibilityProperty); }
            set { SetValue(ControlVisibilityProperty, value); }
        }

        private void btnSpecialButton_Click(object sender, RoutedEventArgs e)
        {
            System.Windows.MessageBox.Show($"The id of the button: {((Button)sender).Tag.ToString()}");
        }
    }
}

XAML

<UserControl x:Class="MultiBindingProblem.SpecialButton"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:MultiBindingProblem"
             mc:Ignorable="d" 
             d:DesignHeight="45" d:DesignWidth="80" 
             x:Name="parent">
    <Grid>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" DataContext="{Binding ElementName=parent}">
            <Button x:Name="btnSpecialButton" Content="Click Me" Click="btnSpecialButton_Click"
                    Tag="{Binding ControlId}"   
                    Visibility="{Binding ControlVisibility}" />
        </StackPanel>
    </Grid>
</UserControl>

TestConverter

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;

namespace MultiBindingProblem
{
    public class TestConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var asset = value as Asset;
            if (asset == null) return Visibility.Hidden;
            return !(asset.PropertyA || asset.PropertyB) ? Visibility.Visible : Visibility.Hidden;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

问题
我可以以某种方式使用Multibinding吗?
或者
当资产对象上的单个属性发生更改时,如何使转换器运行?

【问题讨论】:

  • "当我更改资产对象 PropertyA 的属性时,我希望 MyConverter 应该运行并更改可见性" - 这种预期是错误的。 MultiBinding 到一组属性应该可以工作。

标签: c# wpf data-binding datagrid


【解决方案1】:

您可以将 MultiBinding 与这样的多值转换器一起使用

<Window.Resources>
    <local:MultiBooleanToVisibilityConverter x:Key="MyConverter"/>
</Window.Resources>

<local:SpecialButton ...>
    <local:SpecialButton.ControlVisibility>
        <MultiBinding Converter="{StaticResource MyConverter}">
            <Binding Path="PropertyA"/>
            <Binding Path="PropertyB"/>
        </MultiBinding>
    </local:SpecialButton.ControlVisibility>
</local:SpecialButton>

如果输入属性都不是true,则您当前的转换器实现看起来应该返回Visible。一个等效的多值转换器可能是这样的:

public class MultiBooleanToVisibilityConverter : IMultiValueConverter
{
    public object Convert(
        object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        bool any = values.Any(v => v is bool && (bool)v);

        return any ? Visibility.Hidden : Visibility.Visible;
    }

    public object[] ConvertBack(
        object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

【讨论】:

  • 非常感谢@Clemens。我知道它只是需要一些简单的地方。我对 WPF 不是很熟练,所以我在那里和那里迷路了。一天完全浪费了,现在我可以睡觉了:)再次感谢您!
猜你喜欢
  • 1970-01-01
  • 2011-03-10
  • 1970-01-01
  • 2019-12-12
  • 1970-01-01
  • 1970-01-01
  • 2013-06-26
  • 2012-05-07
  • 1970-01-01
相关资源
最近更新 更多