How to get in ConvertBack parameters from Convert? - c#

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?

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 };
}
}

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).

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>

Related

How to align a Canva child on it's middle to specific coordinates?

I receive some coordinates from my ViewModel and I need to have one child of my WPF Canvas with its center on those coordinates.
How can I achieve this? I tried several RenderTransform, but I'm unable to have the desired effect.
<Canvas>
<SomeControl Canvas.Left="{Binding ToolBarHorizontalPosition}" Canvas.Top="{Binding ToolBarVerticalPosition}"/>
</Canvas>
public MyViewModel: INotifyPropertyChanged{
public double ToolBarHorizontalPosition {get;set;}
public double ToolBarVerticalPosition{get;set;}
public void SomeUpdater(){
ToolBarHorizontalPosition = ...;
ToolBarVerticalPosition= ...;
// Raise property changed
}
}
The thing is that the control will have a dynamic size(it's content will grow), so I cannot compute directly the toolbar position to reference the left/right(because I don't know the width).
Is there a way to make this in the XAML?
Actually, I just found a solution, using a converter that manage to get the position from the ViewModel and the ActualWidth:
public class CenterValueConverter : IMultiValueConverter
{
public object Convert(object[] values,
Type targetType,
object parameter,
CultureInfo culture)
{
if (values == null || values.Length != 2)
{
return null;
}
double originalPosition = (double)values[0];
double size = (double)values[1];
return originalPosition - (size / 2);
}
public object[] ConvertBack(object value,
Type[] targetTypes,
object parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
And then in my XAML for center on the X Axis:
<Canvas>
<SomeControl Name="SomeControl" Canvas.Top="{Binding ToolBarVerticalPosition}">
<Canvas.Left>
<MultiBinding Converter="{StaticResource CenterValuerConverter}">
<Binding Path="ToolBarHorizontalPosition"/>
<Binding ElementName="SomeControl" Path="ActualWidth"/>
</MultiBinding>
</Canvas.Left>
</SomeControl>
</Canvas>

Not controlled exception in WPF XAML: The specified conversion is not valid

I have a MVVM WPF application. I have below converter:
public class PrintIconVisibilityValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] == null || values[1] == null) return Visibility.Collapsed;
int item1 = (int)values[0];
string item2 = (string)values[1];
if (item1 > 0 || !string.IsNullOrEmpty(item2))
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
From my view I do:
<Window.Resources>
<classes:PrintIconVisibilityValueConverter x:Key="PrintIconVisibilityValueConverter"/>
</Window.Resources>
then I have an image in this view:
<Image Source="/MyImages;component/Images/PrintIco.png"
Height="15" Margin="20 0 5 0">
<Image.Visibility>
<MultiBinding Converter="{StaticResource PrintIconVisibilityValueConverter}">
<Binding Path="Item1" />
<Binding Path="Item2" />
</MultiBinding>
</Image.Visibility>
</Image>
Item1 and Item2 are public properties in view model:
private string _item2 = string.Empty;
public string Item2
{
get
{
return _item2;
}
set
{
if (_item2 == value) return;
_item2 = value;
OnPropertyChanged("Item2");
}
}
private int _item1;
public int Item1
{
get
{
return _item1;
}
set
{
if (_item1 == value) return;
_item1 = value;
OnPropertyChanged("Item1");
}
}
It compiles correctly and I can execute the application without problems but in design time, the view is not show, an error says Not controlled exception and points to the line:
int item1 = (int)values[0];
within PrintIconVisibilityValueConverter class.
Below the screenshots of the exception shown on view:
Some suggestions;
Call the GetIsInDesignMode method in your converter and return immediately if it returns true:
public class PrintIconVisibilityValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
return Visibility.Visible;
...
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Set the DataContext in XAML:
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
Set the design time data context:
<Window ... d:DataContext ="{d:DesignInstance {x:Type local:ViewModel}, IsDesignTimeCreatable=True}">
Or Disable XAML UI designer

Philosophy on Binding to Parameters in WPF

So I'm trying to build out a project that will allow a user to type some text into a textbox on the left side of the form and that will filter out the available items from my datasource list.
<Label Content="Enter item name below"></Label>
<TextBox Name="SearchTermTextBox" TabIndex="0" Text="" />
I was under the impression I could bind to the datasource the list then use a converter to filter out the items that were unlike the string.
<ListBox DataContext="{Binding Colors}">
<ListBox.ItemsSource>
<MultiBinding Converter="{StaticResource FilterTextValueConverter}" ConverterParameter="{Binding ElementName=SearchTermTextBox, Path=Text}" />
</ListBox.ItemsSource>
<ListBox.ItemTemplate>
//etc...
</ListBox.ItemTemplate>
</ListBox>
However, you can't bind to an elementname in the converterparameter unless you use something called a dependency property.
Edit: Seeing as I've created confusion with the code above, here's the converter I'm trying to bind:
public class FilterTextValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var trackedColors = value as List<Colors>;
if (trackedColors != null)
return (trackedColors).Where(item => item.ColorName.Contains(parameter.ToString())).ToList();
return null;
}
public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class Colors
{
public String ColorName;
public String Description;
}
What is wrong with my approach here? Clearly I'm angering the WPF gods since this is a fairly straightforward operation but I'm being denied it on principle. Any help would be appreciated.
Simple binding with converter will work here, no need for MultiBinding.
<ListBox ItemsSource="{Binding Path=Text, ElementName=SearchTermTextBox,
Converter="{StaticResource FilterTextValueConverter}">
......
</ListBox>
Assuming FilterTextValueConverter is implementing IValueConverter, you can access text from value passed to Convert method.
public class FilterTextValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
string text = value.ToString(); // TEXT for textBox can be accessed here.
return new List<string>(); // Return filtered list from here.
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
}
UPDATE
In case you want to pass multiple bindings to converter, use IMultiValueConverter because ConverterParameter is not Dependency property, hence cannot be bound.
XAML
<ListBox DataContext="{Binding Colors}">
<ListBox.ItemsSource>
<MultiBinding Converter="{StaticResource FilterTextValueConverter}">
<Binding/>
<Binding ElementName="SearchTermTextBox" Path="Text"/>
</MultiBinding>
</ListBox.ItemsSource>
</ListBox>
Converter
public class FilterTextValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
var trackedColors = values[0] as List<Colors>;
if (trackedColors != null && !String.IsNullOrEmpty(values[1].ToString()))
return (trackedColors).Where(item =>
item.ColorName.Contains(values[1].ToString())).ToList();
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I continued looking into this issue well after the accepted answer was posted and working for me. What I discovered is that it's a fairly trivial task to wrap the control you're trying to get a new dependencyproperty out of to allow for proper binding.
I will not be accepting my own answer to this determined so much later, but this seems (in my amateur opinion) like a much more elegant solution than adding a converter despite being a bit more complex:
Note that this is for a new dependency on the caretindex property of a textbox, not for the original question on binding, but it just requires some smart renaming to get it working ;).
public class TextBoxDependencyWrapper : TextBox
{
public static readonly DependencyProperty CaretIndexProperty = DependencyProperty.Register(
"CaretIndex", typeof (int), typeof (TextBoxDependencyWrapper), new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, CaretIndexChanged ));
protected override void OnKeyUp(KeyEventArgs e) //Event that changes the property we're trying to track
{
base.OnKeyUp(e);
CaretIndex = base.CaretIndex;
}
protected override void OnKeyDown(KeyEventArgs e) //Event that changes the property we're trying to track
{
base.OnKeyDown(e);
CaretIndex = base.CaretIndex;
}
public new int CaretIndex
{
get { return (int) GetValue(CaretIndexProperty); }
set { SetValue(CaretIndexProperty, value); }
}
}

Firing PropertyChanged event when data is bound without path

I have a Listbox bound to an ObservableCollection of ImageMetadata class. Item template of Listbox is defined as
<Image Source="{Binding Converter={StaticResource ImageConverter}}" />
ImageConverter is written as
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var metadata = (ImageMetadata)value;
if (metadata.IsPublic)
{
//code to return the image from path
}
else
{
//return default image
}
}
ImageMetadata is the 'Model' class written as
class ImageMetadata : INotifyPropertyChanged
{
public string ImagePath
{
......
}
public bool IsPublic
{
......
}
}
When an image is updated, I will trigger PropertyChanged event as given below
NotifyPropertyChanged("ImagePath");
Problem here is that : NotifyPropertyChanged event will not work since I am specifying the changed property name as 'ImagePath' and the binding is to 'ImageMetadata' object rather than 'ImagePath' property.
I cannot use
<Image Source="{Binding ImagePath, Converter={StaticResource ImageConverter}}" />
since I need the IsPublic property also to decide which image to display.
How can I modify the code to properly fire PropertyChanged event?
Edit : I am developing for Windows phone 8.
You could use a MultiBinding with a multi-value converter:
<Image>
<Image.Source>
<MultiBinding Converter="{StaticResource ImageConverter}">
<Binding Path="ImagePath"/>
<Binding Path="IsPublic"/>
</MultiBinding>
</Image.Source>
</Image>
The Convert method would look like this:
public object Convert(
object[] values, Type targetType, object parameter,CultureInfo culture)
{
object result = null;
if (values.Length == 2 && values[0] is string && values[1] is bool)
{
var imagePath = (string)values[0];
var isPublic = (bool)values[1];
...
}
return result;
}

How to bind the IsEnabled property to an OR of two values?

currently when I have to make an OR of two values on the IsEnabled property of a control I end using an invisible container control (I use a Border) and setting the IsEnabled of the control and the one of the container.
Is there a better approach? If not, what is the most lightweight control for doing this?
Thanks in advance.
If IsEnabled is set via binding, you may use MultiBinding in conjunction with a multi-value converter.
You can use a converter like this:
public class BooleanOrConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
foreach (object value in values)
{
if ((value is bool) && (bool)value == true)
{
return true;
}
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("BooleanOrConverter is a OneWay converter.");
}
}
And this is how you would use it:
<myConverters:BooleanOrConverter x:Key="BooleanOrConverter" />
...
<ComboBox Name="MyComboBox">
<ComboBox.IsEnabled>
<MultiBinding Converter="{StaticResource BooleanOrConverter}">
<Binding ElementName="SomeCheckBox" Path="IsChecked" />
<Binding ElementName="AnotherCheckbox" Path="IsChecked" />
</MultiBinding>
</ComboBox.IsEnabled>
</ComboBox>
Could use a MultiBinding with a converter which or's the values passed in.

Categories