I have a Prism MVVM app, can easily pass one UIElement as a CommandParameter to ViewModel's Command. But now I want to pass two UIElements. Using this XAML:
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource DummyMultiConverter}">
<Binding ElementName="PasswordBoxType"/>
<Binding ElementName="PasswordBoxRetype"/>
</MultiBinding>
</Button.CommandParameter>
Using this Converter:
public class DummyMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And a simple DelegateCommand in ViewModel:
private DelegateCommand<object> _commandCloseDialogOK;
public DelegateCommand<object> CommandCloseDialogOK =>
_commandCloseDialogOK ??
(_commandCloseDialogOK = new DelegateCommand<object>(commandParameter=> CommandCloseDialogOKExecute(commandParameter)));
public virtual void CommandCloseDialogOKExecute(object commandParameter)
{
RaiseRequestClose(new DialogResult(ButtonResult.OK));
}
public virtual bool CanExecuteCommandCloseDialogOK()
{
return true;
}
When it runs - Convert method gets values correctly, as an array of 2 PasswordBoxes. But CommandCloseDialogOKExecute gets its commandParameter parameter as an array of two nulls. Same happens if I define commandParameter as object[] instead of object. What should I do that commandParameter would be array of two PasswordBoxes?
First, if you are using MVVM you really should not pass view-related data, like UIElement's, to the view model.
Assuming that you know that and this really is a simplified depiction to illustrate your issue, the problem is that the framework explicitly clears the array that is passed into the Convert method before the value that Convert returns gets applied to the binding. The result is that it applies the now-empty array (nulls):
https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Data/MultiBindingExpression.cs,1267
You can avoid this by creating another array in your Convert method:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return new []{values[0], values[1]};
}
Related
I am trying to convert a value on my view model to the type that a control needs. I have an IValueConverter implementation that is being called. It is returning a valid value. However, the value that is returned to the control is not what's returned by the converter.
NOTE Below is a rough example, though not exactly my scenario. I can't post my code as it belongs to the company. Also, I've been unable to replicate the problem. The same scenario in another application works fine.
My converter:
public class TupleCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var members = (IEnumerable<Tuple<string, string>>) value;
var retVal = new ObservableCollection<string>(members.SelectMany(t => new[] {t.Item1, t.Item2}));
return retVal;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And in the XAML:
<ItemsControl ItemsSource="{Binding ValuePairs, Converter={StaticResource MyTupleCollectionConverter}}"/>
When I debug, putting a break point at the return of the converter, I see that it's converting correctly with values, but then I get nothing in the control. Running Snoop, it says that the ItemsSource property is set to null.
I know there are several ways to do it, but I would like to make it even easier if possible because I have a lot of comboboxes to bind in this way. There is a suggestion using ObjectDataProvider here. The problem is that I have to create a resource entry for each enum and that's a lot. So far, I have been using the code-behind way because it's much shorter:
cmb.ItemsSource = Enum.GetValues(typeof(MyTypes));
I'm wondering if an equivalent can be produced in Xaml. I thought we could archive this by using a converter. We could convert the type to an array and then bind the array to combobox' ItemsSource. But I got stuck on how to specify my enum to the converter. Here is my code:
My enum:
public enum MyTypes { Type1, Type2, Type3 };
This is my converter:
public class EnumToArrayConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Enum.GetValues(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null; // I don't care about this
}
}
My Xaml Resource:
<lib:EnumToArrayConverter x:Key="E2A"/>
Here is how to use it:
<ComboBox SelectedItem="{Binding MyType}" ItemsSource="{Binding MyTypes, Converter={StaticResource E2A}}"/>
So, my question is how to specify my enum "MyTypes" to the converter. I also tried to prepend namespace, but it doesn't help.
You would be better off with a MarkupExtension, like this one.
CodeNaked posts a great way of doing this
For your approach to work you can change the converter to Enum.GetValues(value as Type) and use the x:Type syntax as Source for the Binding
ItemsSource="{Binding Source={x:Type local:MyValues},
Converter={StaticResource EnumToArrayConverter}}"
EnumToArrayConverter
public class EnumToArrayConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Enum.GetValues(value as Type);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null; // I don't care about this
}
}
I'm using MVVM, in case it makes a difference.
My MainWindowViewModel has two DependencyProperties, TheList, and TheSelectedItem. TheList is a List<Type>, TheSelectedItem is a Type.
The MainWindow has a ComboBox. When the MainWindowViewModel loads it grabs a list of all the classes in the assembly that implement IMyInterface and sets TheList to this.
Each of these classes has a custom attribute applied called DisplayName, which has one parameter, that will be used to show a user-friendly name for the class instead of the name the application knows about for the class.
I've also got a ValueConverter for the express purpose of converting these types into the display names.
public class TypeToDisplayName : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (targetType.Name == "IEnumerable")
{
List<string> returnList = new List<string>();
if (value is List<Type>)
{
foreach (Type t in value as List<Type>)
{
returnList.Add(ReflectionHelper.GetDisplayName(t));
}
}
return returnList;
}
else
{
throw new NotSupportedException();
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return typeof(BasicTemplate);
}
}
So, what I wind up with is a ComboBox with a list of names in it that the user should be able to understand. Awesome! This is just what I want!
Next step: I bind the SelectedItem property of my ComboBox to my TheSelectedItem property in my ViewModel.
Here's the problem: When I make a selection, I get a little red box around my ComboBox and the TheSelectedItem property on my ViewModel never gets set.
I'm pretty sure it's because of a type mismatch (the items in the ComboBox appear to be strings now, and TheSelectedItem is of type Type--also, when I change TheSelectedItem to a string instead of a Type, it works). But I don't know where I need to start coding to convert the (hopefully unique) DisplayName that's in the ComboBox back to a Type object.
Thanks in advance for any help. I'm pretty stumped on this one.
If I understand your question correctly then you use that Converter on the ItemsSource for the ComboBox? In that case I think you can let the ItemsSource be like it is and instead just Convert each type when they are presented like this.
<ComboBox ...>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=typeName, Converter={StaticResource TypeToDisplayNameConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And then just convert each type in the Converter.
public class TypeToDisplayNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Type t = (Type)value;
return ReflectionHelper.GetDisplayName(t);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}
Make sure you have IsSynchronizedWithCurrentItem set to true on the ComboBox. Check this out...
I have a PriorityBinding
<PriorityBinding FallbackValue="Bindings were null">
<Binding Path="Foo" />
<Binding Path="Bar" />
</PriorityBinding>
I'd like to make it so if Foo is null it will use Bar and if both are null it will use the FallbackValue. However null is a valid value for this property because it only expects an object.
Is there any way to make the PriorityBinding advance to the next binding when the value is null? I'd prefer to do it in XAML, but if I can't I'll just make a converter for it.
Edit
I ended up just writing a converter for it
public class NullToDependencyPropertyUnsetConverter
: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value ?? DependencyProperty.UnsetValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I'd go with the valueconverter returning UnsetValue if the bound value is null.
PriorityBindings are more useful if you want to share a datatemplate between different types of objects.
Is there any way to bind a value to a textblock that is obtained from a method. For example, I pass my Person object into the HierarchicalDataTemplate, from there I can access its Weight property. Now lets say I want to get the weight in mars, I would call the InMars method that takes a parameter of int EarthWeight . Now earthweight is going to change from Person to Person, how can this parameter be set every time?
The best way to do this is with a converter.
public class WeightOnMarsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// value will be the persons weight
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("This method should never be called");
}
}
Then you just need to set up the binding.
<l:WeightOnMarsConverter x:key="weightOnMars" /> <-- Add this to the resources
{Binding Path=Weight, Converter={StaticResource weightOnMars}}