WPF Converter не работает, когда привязка ко всему объекту и некоторые свойства были изменены - PullRequest
0 голосов
/ 05 марта 2020

Я достиг своего умственного барьера и не в состоянии понять это. Я уверен, что упускаю что-то простое, но я застрял. Код ниже - это минимальный код, необходимый для решения моей проблемы, но он далек от моего производственного кода.

Настройка:
У меня есть окно WPF с DataGrid элемент управления, связанный с бизнес-объектом , который включает набор активов . Для каждого актива мне нужно отобразить пользовательский элемент управления (SpecialButton), который visibility определяется на основе нескольких свойств объекта актива . Когда я нажимаю кнопку ( в моем примере, у меня есть дополнительная кнопка, которая для простоты меняет свойства ), она изменяет свойство базового объекта актива, что должно скрывать элемент управления.

Задача
Я связываю свойство элемента управления пользователя ControlVisibility со всем объектом актива {Binding .}
<local:SpecialButton x:Name="buttonOnEachRow" ControlId="{Binding Id}" ControlVisibility="{Binding ., Converter={StaticResource MyConverter}}"/>
Когда я изменяю свойство объекта Объект актива PropertyA Я ожидаю, что MyConverter должен запуститься и изменить значение видимости, но этого не произойдет.

То, что я пробовал
Я пробовал так много вещей что я даже не помню Наиболее многообещающим кажется MultipleBinding, но я не смог понять, как написать синтаксис для свойства ControlVisibility. Я попытался установить некоторые параметры на элементе управления DataGrid, изменив способ обновления user control, но без vail.
В качестве обходного пути в своем рабочем коде я создал свойство fake, которое выполняет logi c который в данный момент находится в конвертере и привязывает 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>

Пользовательский элемент управления (SpecialButton)

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?
Или
Как сделать конвертер запустить, когда изменилось одно свойство объекта актива?

1 Ответ

1 голос
/ 05 марта 2020

Вы бы использовали 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>

Ваша текущая реализация преобразователя выглядит так, как будто она должна вернуть Visible, если ни одно из входных свойств не равно true. Эквивалентный многозначный преобразователь может быть таким:

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();
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...