2

The problem:

I am using MultiBinding with converter to pass (x,y) coordinates into method.

And I can't make it working in back direction:

public class MyConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var x = (int)values[0];
        var y = (int)values[1];
        return Model.Get(x, y);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        Model.Set(x, y, value); // how to get x, y here?
        return new object[] { Binding.DoNothing, Binding.DoNothing };
    }
}

Additional info:

The data will be visualized in a form of table. Here is cell template:

<TextBox>
    <TextBox.Text>
        <MultiBinding Converter="{StaticResource converter}" Mode="TwoWay">
            <Binding Path="X" Mode="OneWay" />
            <Binding Path="Y" Mode="OneWay" RelativeSource="..." />
        </MultiBinding>
    </TextBox.Text>
</TextBox>

The idea is to use converter, which receive x (from cell view model) and y (from parent column view model, notice RelativeSource) and calls Get(x,y) to display value.

However, when user entered something, ConvertBack is called and I need to call Set(x, y, value) method.

How do I pass x and y into ConvertBack?

Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • What `Model.Get` returns? – Maxim Aug 03 '17 at 13:20
  • How do you call `ConvertBack` or how do you wana call it? – Amr Elgarhy Aug 03 '17 at 13:22
  • 2
    You cannot pass x and y to ConvertBack method. It's ConvertBack method's job to convert input value to x and y. You need a method that can return x and y (for example, in your Model.) If the users input is string, you have to parse this string and get x and y. – Yevgeniy Aug 03 '17 at 13:25
  • @AmrElgarhy, I am not calling `ConvertBack`, it's WPF magic. Its called if `MultiBinding.Mode=TwoWay` and user changes the value. – Sinatr Aug 03 '17 at 13:31
  • 1
    @Yevgeniy, **cannot** is not an answer I am agree with. Currently I am thinking to use `Dictionary`, but then I have problem with passing the key, which should be instance of control, I was hoping for an easier approach. Notice one-way binding to `x` and `y`, I don't have intention to change them, only to call `Set` method when user changes text. – Sinatr Aug 03 '17 at 13:34
  • @Sinatr ahaa, thank you for explaining it, I am not a WPF expert, i just thought it is a normal method. – Amr Elgarhy Aug 03 '17 at 13:35
  • @Maxim, it will return some string. Returned string depends on `x,y`. Simply. – Sinatr Aug 03 '17 at 13:37
  • Could you try to return an object that would consist of 2 objects that you are passing and then on convert back you should get that object back. If that makes sense. – XAMlMAX Aug 03 '17 at 13:38
  • @Sinatr, The purpose of MultiValueConverter is to convert multiple values from different sources to one value displayed in UI(Convert method). And to convert user input (as one value) to multiple values for different sources (ConvertBack method). there is no place to get these values except from user input value. Even if you could pass something to ConvertBack method, what exactly values you would pass? – Yevgeniy Aug 03 '17 at 13:53
  • @XAMlMAX, it's a binding to `TextBox.Text`, so it has to be a text, but your idea is really interesting one, thanks. Let me think over it. – Sinatr Aug 03 '17 at 13:54
  • 1
    There is also possibility of you adding `DependencyProperties` on your Converter so you can Bind to them. So when ConvertBack is called it triggers those properties. – XAMlMAX Aug 03 '17 at 13:56

3 Answers3

5

There might be more-or-less dirty workarounds to get such a multivalue converter working. But I'd suggest you keep your multivalue converter one-way, but return a container object that wraps the actual text property.

Instead of directly binding to the TextBox.Text property, bind to some other property (eg. DataContext or Tag) and then bind the text to the container value.

Small example:

<TextBox Text="{Binding Value}">
    <TextBox.DataContext>
        <MultiBinding Converter="{StaticResource cMyConverter}">
            <Binding Path="X"/>
            <Binding Path="Y"/>
        </MultiBinding>
    </TextBox.DataContext>
</TextBox>

With container and converter:

public class ValueProxy
{
    public int X { get; set; }
    public int Y { get; set; }

    public string Value
    {
        get { return Model.Get(X, Y); }
        set { Model.Set(X, Y, value); }
    }
}
public class MyConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var x = (int)values[0];
        var y = (int)values[1];
        return new ValueProxy { X = x, Y = y };
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return new object[] { Binding.DoNothing, Binding.DoNothing };
    }
}
grek40
  • 13,113
  • 1
  • 24
  • 50
  • I like this approach! This way `TextBox` will call the ToString() on the Proxy object. – XAMlMAX Aug 03 '17 at 14:31
  • @XAMlMAX wait, what? How would it call `ToString()`? – grek40 Aug 03 '17 at 14:32
  • If you set the `Text` property to `Binding .` it will call `ToString()` on the `DataContext` object. – XAMlMAX Aug 03 '17 at 14:33
  • 1
    @XAMlMAX thats why I bind to `Value`... binding to `.` would defeat the desired `TwoWay` binding on `Text` – grek40 Aug 03 '17 at 14:34
  • OK I still like it and upvote will remain, at least that is the way I would used it, if I had to achieve something like this. Thanks – XAMlMAX Aug 03 '17 at 14:36
  • I was posting my answer, when I saw your. There is some difference in details (so I keep mine), but in general idea is the same. Thanks. – Sinatr Aug 03 '17 at 14:41
  • This helped me greatly in 2021. Had to use Tag instead of Datacontext because the whole thing was wrapped inside a listview. – user3486991 Jul 06 '21 at 22:03
0

The short answer is that you can't directly get the values of xand y inside your ConvertBack method. The IMultiValueConverter Convert multiple values into a single value. So, the ConvertBack method will do the opposite: convert a single value into multiple values.

It all depends on what your Model.Get(x, y) method returns. It needs to return a value that is unique enough for you to get the separate values of x and y from it.

Example: create unique strings for each pair of (x,y).

Daniel Marques
  • 683
  • 8
  • 17
0

It seems hard to pass parameters into ConvertBack. It might be possible, but there is a workaround, which makes ConvertBack unnecessary. Thanks to @XAMlMAX for an idea.

One possibility to achieve it (there could be a better way) is to use data templates. Instead of multi-binding TextBlock.Text with string we can bind ContentControl.Content with some viewmodel, and this viewmodel should do the rest, including Set(x, y, value) call.

Here is code:

public class ViewModel
{
    public int X { get; set; }
    public int Y { get; set; }

    string _text;
    public string Text
    {
        get { return _text; }
        set
        {
            // this should be only called by the view
            _text = value;
            Model.Set(X, Y, value);
        }
    }

    public ViewModel(string text)
    {
        _text = text;
    }
}

public class MyConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var x = (int)values[0];
        var y = (int)values[1];
        return new ViewModel(Model.Get(x, y)) { X = x, Y = y };
    }

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

and the xaml will become

<ContentControl Focusable="False">
    <ContentControl.Content>
        <MultiBinding Converter="{StaticResource converter}">
            <Binding Path="X" />
            <Binding Path="Y" RelativeSource="..."/>
        </MultiBinding>
    </ContentControl.Content>
</ContentControl>

where data template is

<DataTemplate DataType="{x:Type local:ViewModel}">
    <TextBox Text="{Binding Text}" />
</DataTemplate>
Sinatr
  • 20,892
  • 15
  • 90
  • 319