Проблема MultiBinding ConvertBack

Пытаясь решить проблему, возникшую у меня в другом проекте, я создал следующий пример для репликации проблемы.

Идея состоит в том, что, когда пользователь вводит новые значения через ползунок или текстовое поле, эти значения должны быть "ConvertedBack" через конвертер, а источник обновляться. Я, кажется, не вижу этого, хотя, я полагаю, из-за того факта, что свойства InternalRep записываются, но не информируют о выражении bindex для InternalRepProperty.

Как лучше всего решить эту проблему?

Один из методов, который я попытался, состоял в том, чтобы обработать событие ползунков ValueChanged, но это заставило конвертор... ConvertBack, затем Convert, затем ConvertBack, затем Convert, не знаю, почему.

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

TextSplitter XAML

<ContentControl x:Class="WpfApplication23.TextSplitter"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:local="clr-namespace:WpfApplication23"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <UniformGrid Columns="3" Rows="2">
        <TextBox Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextSplitter}}, 
                 Path=InternalRep.First, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />

        <TextBox Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextSplitter}}, 
                 Path=InternalRep.Second, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />

        <TextBox Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextSplitter}}, 
                 Path=InternalRep.Third, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />

        <Slider  Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextSplitter}}, 
                 Path=InternalRep.First, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Maximum="255" />

        <Slider  Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextSplitter}}, 
                 Path=InternalRep.Second, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Maximum="255" />

        <Slider  Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:TextSplitter}}, 
                 Path=InternalRep.Third, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Maximum="255" ValueChanged="OnSliderChnaged" />
    </UniformGrid>

</ContentControl>

TextSplitter C#

public class InternalRep
    {
        public int First { get; set; }
        public int Second { get; set; }
        public int Third { get; set; }
    };

    public class LettersToInternalRepMultiConvertor : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType,
               object parameter, System.Globalization.CultureInfo culture)
        {
            InternalRep ir = new InternalRep()
            {
                First = (int)(char)values[0],
                Second = (int)(char)values[1],
                Third = (int)(char)values[2],
            };

            return ir;
        }

        public object[] ConvertBack(object value, Type[] targetTypes,
               object parameter, System.Globalization.CultureInfo culture)
        {
            InternalRep ir = (InternalRep)value;
            if (ir != null)
            {
                return new object[] 
                { 
                    (char)ir.First, 
                    (char)ir.Second, 
                    (char)ir.Third 
                };
            }
            else
            {
                throw new Exception();
            }
        }
    }

    public partial class TextSplitter : ContentControl
    {
        public static readonly DependencyProperty FirstProperty = DependencyProperty.Register(
        "First", typeof(char), typeof(TextSplitter));

        public static readonly DependencyProperty SecondProperty = DependencyProperty.Register(
        "Second", typeof(char), typeof(TextSplitter));

        public static readonly DependencyProperty ThirdProperty = DependencyProperty.Register(
        "Third", typeof(char), typeof(TextSplitter));

        public static readonly DependencyProperty InternalRepProperty = DependencyProperty.Register(
        "InternalRep", typeof(InternalRep), typeof(TextSplitter));

        BindingExpressionBase beb = null;

        public TextSplitter()
        {
            InitializeComponent();

            MultiBinding mb = new MultiBinding();
            mb.Mode = BindingMode.TwoWay;
            mb.Bindings.Add(SetBinding("First"));
            mb.Bindings.Add(SetBinding("Second"));
            mb.Bindings.Add(SetBinding("Third"));
            mb.Converter = new LettersToInternalRepMultiConvertor();

            beb = SetBinding(InternalRepProperty, mb);
        }

        Binding SetBinding(string _property)
        {
            Binding b = new Binding(_property);
            b.Mode = BindingMode.TwoWay;
            b.Source = this;
            return b;
        }

        public char First
        {
            get { return (char)GetValue(FirstProperty); }
            set { SetValue(FirstProperty, value); }
        }

        public char Second
        {
            get { return (char)GetValue(SecondProperty); }
            set { SetValue(SecondProperty, value); }
        }

        public char Third
        {
            get { return (char)GetValue(ThirdProperty); }
            set { SetValue(ThirdProperty, value); }
        }
    }

MainWindow XAML

<Window x:Class="WpfApplication23.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication23"
        Title="MainWindow" Height="640" Width="800" WindowStartupLocation="CenterScreen">
    <StackPanel>
        <local:TextSplitter First="{Binding A, Mode=TwoWay}"
                            Second="{Binding B, Mode=TwoWay}"
                            Third="{Binding C, Mode=TwoWay}"/>
    </StackPanel>
</Window>

Код главного окна

namespace WpfApplication23
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }

        char m_a = 'a';
        public char A
        {
            get { return m_a; }
            set { m_a = value; }
        }

        char m_B = 'b';
        public char B
        {
            get { return m_B; }
            set{ m_B = value; }
        }

        char m_c = 'c';
        public char C
        {
            get { return m_c; }
            set { m_c = value; }
        }

    }
}

1 ответ

Вам необходимо реализовать INotifyPropertyChanged в вашей ViewModel.
Здесь ViewModel - это окно, поэтому вы должны сделать:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        this.DataContext = this;
        InitializeComponent();
    }

    char m_a = 'a';
    public char A
    {
        get { return m_a; }
        set
        {
            if (value != m_a)
            {
                m_c = value;
                RaisePropertyChanged("A");
            }
        }
    }

    char m_B = 'b';
    public char B
    {
        get { return m_B; }
        set
        {
            if (value != m_B)
            {
                m_c = value;
                RaisePropertyChanged("B");
            }
        }
    }

    char m_c = 'c';
    public char C
    {
        get { return m_c; }
        set
        {
            if (value != m_c)
            {
                m_c = value;
                RaisePropertyChanged("C");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged(string _Prop)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(_Prop));
        }
    }

    DelegateCommand _RecomputeCommand;
    public DelegateCommand RecomputeCommand
    {
        get { return _RecomputeCommand ?? (_RecomputeCommand = new DelegateCommand(Recompute)); }
    }

    public void Recompute()
    {
        //Do something with A, B and C.
    }
}

РЕДАКТИРОВАТЬ: вам просто нужно связать 3 ползунка с A, B, C (вам понадобится вышеуказанная реализация), а затем действовать с RecomputeCommand следующим образом:

<StackPanel>
    <Slider Value="{Binding A}" Maximum="255">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="ValueChanged">
                <i:InvokeCommandAction Command="{Binding RecomputeCommand}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Slider>
    <Slider Value="{Binding B}" Maximum="255">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="ValueChanged">
                <i:InvokeCommandAction Command="{Binding RecomputeCommand}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Slider>
    <Slider Value="{Binding C}" Maximum="255">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="ValueChanged">
                <i:InvokeCommandAction Command="{Binding RecomputeCommand}" />
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Slider>
</StackPanel>

Конечно это может быть в ContentControl по мере необходимости.

Другие вопросы по тегам