I want to bind dictionary to CommandParameter of button.
This my xaml code:
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding>
<Binding.Source>
<system:String>SomeString</system:String>
</Binding.Source>
</Binding>
<Binding>
<Binding.Source>
<models:StringObjectPair Key="UserId" Value="{Binding User.Id, UpdateSourceTrigger=PropertyChanged}" />
</Binding.Source>
</Binding>
<Binding>
<Binding.Source>
<models:StringObjectPair Key="UserName" Value="{Binding User.Name, UpdateSourceTrigger=PropertyChanged}" />
</Binding.Source>
</Binding>
</MultiBinding>
</Button.CommandParameter>
StringObjectPair class:
public class StringObjectPair : FrameworkElement
{
public string Key { get; set; }
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(object), typeof(StringObjectPair), new PropertyMetadata(defaultValue: null));
public object Value
{
get { return GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
}
In the MyMultiValueConverter into the values[1].Value and values[2].Value properties I see nulls, but User.Id and User.Name not equals null.
In the output window no errors.
How can I bind this?
First of all, for this kind of scenarios, it is much easier to use move the logic from converter to viewmodel. All inputs for the command should be accessible from viewmodel. Then you don't need command parameter at all:
public class MainWindowViewModel
{
private User _user;
public MainWindowViewModel()
{
MyCommand = new DelegateCommand(() =>
{
//Here you can access to User.Id, User.Name or anything from here
});
}
public DelegateCommand MyCommand { get; private set; }
public User User
{
get { return _user; }
set
{
if (value == _user) return;
_user = value;
OnPropertyChanged();
}
}
}
Second, your original Binding to User.Id didn't worked, because the StringObjectPairs you have created are not in VisualTree and therefore don't have datacontext inherited from parent. However, why not to simplify the multibinding:
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding Source="SomeString" />
<Binding Path="User.Id" />
<Binding Path="User.Name" />
</MultiBinding>
var someString = (string)values[0];
var userId = (int)(values[1] ?? 0);
var userName = (string)values[2];
I can imagine even simpler solutution using without multibinding:
<Button Command="..."
CommandParameter="{Binding User, Converter={StaticResource MyConverter},
ConverterParameter=SomeString}" />
public class MyConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var user = (User)value;
var someString = (string)paramenter;
int userId = user.Id;
string userName = user.Name;
return ...
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
EDIT:
if you insist on passing key value pairs to command (although I don't consider it to be a good practice) you can simplify the xaml like this:
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding Source="SomeString" ConverterParameter="SomeString" Converter="{StaticResource KeyValueConverter}" />
<Binding Path="User.Id" ConverterParameter="Id" Converter="{StaticResource KeyValueConverter}"/>
<Binding Path="User.Name" ConverterParameter="Name" Converter="{StaticResource KeyValueConverter}"/>
</MultiBinding>
public class KeyValueConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return new KeyValuePair<string, object>((string)parameter, value);
}
}
public class DictionaryMultiConverter : IMultiValueConverter
{
public object Convert(object[] keyValues, Type targetType, object parameter, CultureInfo culture)
{
return keyValues.Cast<KeyValuePair<string, object>>()
.ToDictionary(i => i.Key, i => i.Value);
}
}
I found the solution
Changes in StringObjectPair class:
public class StringObjectPair
{
public string Key { get; set; }
public object Value { get; set; }
}
Changes in XAML:
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MyMultiValueConverter}">
<Binding /><!--This is DataContext-->
<Binding>
<Binding.Source>
<system:String>SomeString</system:String>
</Binding.Source>
</Binding>
<Binding>
<Binding.Source>
<!--Just string in Value-->
<models:StringObjectPair Key="UserId" Value="User.Id" />
</Binding.Source>
</Binding>
<Binding>
<Binding.Source>
<models:StringObjectPair Key="UserName" Value="User.Name" />
</Binding.Source>
</Binding>
</MultiBinding>
</Button.CommandParameter>
And in MyMultiValueConverter I just get the properties on the basis of a Value:
var dataContext = values[0];
var someString = values[1] as string;
var parameters = new Dictionary<string, object>();
for (var i = 2; i < values.Length; i++)
{
var pair = values[i] as StringObjectPair;
if (!string.IsNullOrEmpty(pair?.Key) && !parameters.ContainsKey(pair.Key))
{
var props = (pair.Value as string)?.Split('.') ?? Enumerable.Empty<string>();
var value = props.Aggregate(dataContext, (current, prop) => current?.GetType().GetProperty(prop)?.GetValue(current, null));
parameters.Add(pair.Key, value);
}
}
Related
In the code below I'm trying to display "RateValue" as both a decimal and a percentage using a converter.
The below GridColumn code is within a ComboBoxEdit popout template.
What I'm seeing is that when all GridColumns are added the underlying "RateValue" ends up being the same in both cases. However when I only have one or the other they are showing the right values.
So having both appears to be changing the underlying source value.
Am I missing something obvious here?
Thanks
<dxg:GridColumn MinWidth="80" Header="Rate (%)">
<dxg:GridColumn.Binding>
<Binding Path="RateValue" Converter="{StaticResource DecimalToFourDecimalPlacesPercentageConverter}" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
</dxg:GridColumn.Binding>
</dxg:GridColumn>
<dxg:GridColumn Header="Rate (Decimal)">
<dxg:GridColumn.Binding>
<Binding Path="RateValue" Converter="{StaticResource DecimalToFourDecimalPlacesConverter}" Mode="OneWay" UpdateSourceTrigger="PropertyChanged"/>
</dxg:GridColumn.Binding>
</dxg:GridColumn>
<converters1:NumericToStringConverter x:Key="DecimalToFourDecimalPlacesPercentageConverter" Format="0:N4" Multiplier="100"/>
<converters1:NumericToStringConverter x:Key="DecimalToFourDecimalPlacesConverter" Format="0:N4" Multiplier="1"/>
public class NumericToStringConverter : IValueConverter
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(NumericToStringConverter));
public string Format { get; set; }
public int Multiplier { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is decimal)) return Binding.DoNothing;
try
{
var v = (decimal?) value;
return string.Format("{" + Format + "}", Multiplier*v);
}
catch (FormatException ex)
{
Logger.Error(string.Format("Failed to format '{0}'", value), ex);
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I'm not familiar with DevExpress's WPF control, but assume they work like official one, there's no need for converter, StringFormat should be enough.
<dxg:GridColumn MinWidth="80" Header="Rate (%)">
<dxg:GridColumn.Binding>
<Binding Path="RateValue" StringFormat="P4"/>
</dxg:GridColumn.Binding>
</dxg:GridColumn>
<dxg:GridColumn Header="Rate (Decimal)">
<dxg:GridColumn.Binding>
<Binding Path="RateValue" StringFormat="N4"/>
</dxg:GridColumn.Binding>
</dxg:GridColumn>
If that doesn't work, change your converter to
public class NumericToStringConverter : IValueConverter
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(NumericToStringConverter));
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
return string.Format(parameter.ToString(), value);
}
catch (FormatException ex)
{
Logger.Error($"Failed to format {value}", ex);
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
then change other parts to
<dxg:GridColumn MinWidth="80" Header="Rate (%)">
<dxg:GridColumn.Binding>
<Binding Path="RateValue" Converter="{StaticResource MyDecimalToStringConverter}"
ConverterParameter="P4"/>
</dxg:GridColumn.Binding>
</dxg:GridColumn>
<dxg:GridColumn Header="Rate (Decimal)">
<dxg:GridColumn.Binding>
<Binding Path="RateValue" Converter="{StaticResource MyDecimalToStringConverter}"
ConverterParameter="N4"/>
</dxg:GridColumn.Binding>
</dxg:GridColumn>
<converters1:NumericToStringConverter x:Key="MyDecimalToStringConverter"/>
I have simple button which do specific action like edit element. And I would like to pass a few parameters including object specifically SelectedMatch from datagrid and SelectedDate from callendar. Now i have something like this :
<DatePicker x:Name="dpEditDateMatch" SelectedDateFormat="Long" SelectedDate="{Binding MyDateTimeProperty2, Mode=TwoWay}" ></DatePicker>
<Button Content="Edit match" Command="{Binding EditMatchCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource Converter}">
<Binding >??</Binding>
<Binding Path="SelectedDate" ElementName="dpEditDateMatch"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
<ScrollViewer>
<DataGrid IsReadOnly="True" ItemsSource="{Binding match}" SelectedItem="{Binding SelectedMatch}"/>
</ScrollViewer>
In .cs file i have definition of SelectedMatch like this:
object _SelectedMatch;
public object SelectedMatch
{
get
{
return _SelectedMatch;
}
set
{
if (_SelectedMatch != value)
{
_SelectedMatch = value;
RaisePropertyChanged("SelectedMatch");
}
}
}
How can i do that ? I mean, handle this from xaml.
I just faced the problem you said yesterday.
And I solved it now.
Here is the XAML:
<Page.Resources>
<local:MultiIcommandParameterConverter x:Key="MultiIcommandParameterConverter"></local:MultiIcommandParameterConverter>
</Page.Resources>
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel></WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding ButtonClick}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource MultiIcommandParameterConverter}">
<Binding Path="DetailIndex"/>
<Binding Path="DetailContent"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And here is code behind of INotifyPropertyChanged:
public class DetailButtonClass: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChange(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
string _DetailContent;
public string DetailContent
{
get
{
return _DetailContent;
}
set
{
_DetailContent = value;
NotifyPropertyChange("DetailContent");
}
}
string _DetailIndex;
public string DetailIndex
{
get
{
return _DetailIndex;
}
set
{
_DetailIndex = value;
NotifyPropertyChange("DetailIndex");
}
}
CommandClass _ButtonClick;
public override CommandClass ButtonClick
{
get
{
return _ButtonClick;
}
set
{
_ButtonClick = value;
NotifyPropertyChange("ButtonClick");
}
}
public class CommandClass : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public delegate void DelegateClickVoid(string index,string content);
public DelegateClickVoid ClickVoid = null;
public void Execute(object parameter)
{
string[] parameterArray = parameter.ToString().Split(",".ToArray());
ClickVoid?.Invoke(parameterArray[0], parameterArray[1]);
}
}
}
And here is code behind about IMultiValueConverter,the most important things:
public class MultiIcommandParameterConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0] + "," + values[1];
}
object[] IMultiValueConverter.ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Totally,convert all the parameters into one parameter by using ',' to split it.While the button clicked,split the parameter by ',' into parameters.
You already have the data in your ViewModel i.e. MyDateTimeProperty2 which is for your date and SelectedMatch for the match. In your MultiBinding you should pass this:
<Button Content="Edit match" Command="{Binding EditMatchCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource Converter}">
<Binding Path="MyDateTimeProperty2" />
<Binding Path="SelectedMatch" />
</MultiBinding>
</Button.CommandParameter>
</Button>
Because Button already has the same DataContext as the DatePicker and DataGrid you don't even need that x:Name="dpEditDateMatch" on the DatePicker to get the data.
I have user control with dependency property selected items(which is a collection of enum values)
public IEnumerable SelectedItems
{
get { return (IEnumerable)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IEnumerable),
typeof(UserControl1), new FrameworkPropertyMetadata(OnChangeSelectedItems)
{
BindsTwoWayByDefault = true
});
private static void OnChangeSelectedItems(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uc = d as UserControl1;
if (uc != null)
{
var oldObservable = e.OldValue as INotifyCollectionChanged;
var newObservable = e.NewValue as INotifyCollectionChanged;
if (oldObservable != null)
{
oldObservable.CollectionChanged -= uc.SelectedItemsContentChanged;
}
if (newObservable != null)
{
newObservable.CollectionChanged += uc.SelectedItemsContentChanged;
uc.UpdateMultiSelectControl();
}
}
}
private void SelectedItemsContentChanged(object d, NotifyCollectionChangedEventArgs e)
{
UpdateMultiSelectControl();
}
I have binded selectedItems dependency property with a checkbox in my user control using a converter
<ItemsControl ItemsSource="{Binding Items}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<CheckBox x:Name="ChkBox" Margin="13 0 0 10" Content="{Binding}" >
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource CheckBoxConverter}" Mode="TwoWay" >
<Binding ElementName="ChkBox" Path="Content" />
<Binding RelativeSource="{RelativeSource FindAncestor,AncestorType={x:Type UserControl}}" Path="DataContext.SelectedItems" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay"></Binding>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and converter
public class CheckBoxConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
if (values != null && values.Length > 1)
{
var content = values[0];
var selected = values[1] as IList;
if (selected != null && selected.Contains(content))
{
return true;
}
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, System.Globalization.CultureInfo culture)
{
return new[] { Binding.DoNothing, Binding.DoNothing };
}
}
My problems is if I add values in Selected items at the time of construction the converter is getting called but if I add values on a button click ,its not getting called.Can anybody tell me the reason why its happening and how i can correct this one.
The Convert method of the converter only gets called when any of the properties that you bind to changes. You are binding to the Content property of the CheckBox and the SelectedItems property of the UserControl. Neither of these change when you add a new item to the collection.
Try to also bind to the Count property of the collection:
<CheckBox x:Name="ChkBox" Margin="13 0 0 10" Content="{Binding}" >
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource CheckBoxConverter}" Mode="TwoWay" >
<Binding ElementName="ChkBox" Path="Content" />
<Binding RelativeSource="{RelativeSource AncestorType={x:Type UserControl}}" Path="SelectedItems"/>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type UserControl}}" Path="SelectedItems.Count"/>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
I have something like this:
<Controls:ToggleRectangleButton.Visibility>
<MultiBinding Converter="{StaticResource MultiButtonCheckedToVisibilityConverter}">
<Binding ElementName="btDayAndNightsLinesTickets" Path="IsButtonChecked" />
<Binding ElementName="btSchoolSemester" Path="IsButtonChecked" />
</MultiBinding>
</Controls:ToggleRectangleButton.Visibility>
MultiButtonCheckedToButtonEnabledConverter's convert method
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool visible = false;
foreach (object value in values)
{
if (value is bool)
{
if ((bool)value == true) visible = true;
}
}
if (visible)
{
return System.Windows.Visibility.Visible;
}
else
{
return System.Windows.Visibility.Hidden;
}
}
So it mean that if at least one of buttons passed as parameters has IsButtonChecked property set to true -> show control. Otherwise hide it.
I want to add some functionality, that is condition:
if ( otherButton.IsChecked ) return System.Windows.Visibility.Hidden;
So if otherButton is checked hide control (independently of the other conditions). I want to be able to set more "otherButtons" than 1 (if at least one of "otherButtons" is checked -> Hide).
Try this:
public class MultiButtonCheckedToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool visible = false;
int trueCount = (int)parameter;
for (int i = 0; i < trueCount; i++)
{
if ((bool)values[i])
{
visible = true;
break;
}
}
if (visible)
{
for (int i = trueCount; i < values.Length; i++)
{
if (!(bool)values[i])
{
visible = false;
break;
}
}
}
if (visible)
{
return System.Windows.Visibility.Visible;
}
else
{
return System.Windows.Visibility.Hidden;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Button Content="Test">
<Button.Visibility>
<MultiBinding Converter="{StaticResource MultiButtonCheckedToVisibilityConverter}">
<MultiBinding.ConverterParameter>
<sys:Int32>2</sys:Int32>
</MultiBinding.ConverterParameter>
<Binding ElementName="btDayAndNightsLinesTickets" Path="IsChecked" />
<Binding ElementName="btSchoolSemester" Path="IsChecked" />
<Binding ElementName="btOther1" Path="IsChecked" />
<Binding ElementName="btOther2" Path="IsChecked" />
</MultiBinding>
</Button.Visibility>
</Button>
<ToggleButton Name="btDayAndNightsLinesTickets">btDayAndNightsLinesTickets</ToggleButton>
<ToggleButton Name="btSchoolSemester">btSchoolSemester</ToggleButton>
<ToggleButton Name="btOther1">btOther1</ToggleButton>
<ToggleButton Name="btOther2">btOther2</ToggleButton>
The idea is to tell to converter how many buttons shows the control. If this count is not a constant you can refactor converter to receive count as a first binding.
the order of the binding will be kept in the converter code.
you can check the object[] values using an Index and implement your logic according to it.
for example :
if((values[0] is bool) && ((bool)values[0]))
{
//DoSomething
}
I'm trying to pass in an array of system:Object to my converter as a parameter:
Xaml: Doesn't work
<TextBlock.Text>
<Binding ElementName="MainGrid" Path="DataContext" Converter="{StaticResource TestConverter}">
<Binding.ConverterParameter>
<x:Array Type="system:Object">
<Binding ElementName="MainGrid" Path="DataContext" />
<Binding ElementName="SomeOtherElement" Path="DataContext" />
</x:Array>
</Binding.ConverterParameter>
</Binding>
</TextBlock.Text>
Following XAML Does work, I found a sample online that used an array of Brush:
<TextBlock.Text>
<Binding ElementName="MainGrid" Path="DataContext" Converter="{StaticResource TestConverter}">
<Binding.ConverterParameter>
<x:Array Type="Brush">
<SolidColorBrush Color="LawnGreen"/>
<SolidColorBrush Color="LightSkyBlue"/>
<SolidColorBrush Color="LightCoral"/>
</x:Array>
</Binding.ConverterParameter>
</Binding>
</TextBlock.Text>
I get a System.Windows.Markup.XamlParseException: A 'Binding' cannot be used within a 'ArrayList' collection. A 'Binding' can only be set on a DepedencyProperty or a DependencyObject.
I've tried one of the suggested answers e.g. adding the ViewModel as a Dependency Object to my converter but that isn't working
public class TestConverter : DependencyObject , IValueConverter
{
public static readonly DependencyProperty PropertyTypeProperty = DependencyProperty.Register(
"PropertyType", typeof (DerivedRacingViewModel), typeof (TestConverter), new PropertyMetadata(default(DerivedRacingViewModel)));
public DerivedRacingViewModel PropertyType
{
get { return (DerivedRacingViewModel) GetValue(PropertyTypeProperty); }
set { SetValue(PropertyTypeProperty, value); }
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var x = parameter;
return "Test";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var x = parameter;
var z = parameter;
throw new NotImplementedException();
}
}
Then changing my xaml to:
<converters:TestConverter x:Key="TestConverter" DerivedRacingViewModel="{Binding}" />
That give me compile time errors:
'DerivedRacingViewModel' was not found in type 'TestConverter'.
The reason behind doing this is I want to have 2 or 3 objects available when I'm doing my ConvertBack, e.g. I need the text that is entered into text box, the value that text box is bound to and the view model. This is where I'm having real difficulty. I've seen other people doing it by splitting strings and stuff but I really don't like that.
You should use an ItemsControl like below :
<TextBlock.Text>
<Binding ElementName="MainGrid" Path="DataContext" Converter="{StaticResource TestConverter}">
<Binding.ConverterParameter>
<ItemsControl>
<ItemsControl.Items>
<Label Content="{Binding ElementName=MainGrid, Path=DataContext}"/>
<Label Content="{Binding ElementName=SomeOtherElement, Path=DataContext}"/>
</ItemsControl.Items>
</ItemsControl>
</Binding.ConverterParameter>
</Binding>
</TextBlock.Text>
TestConverter
public class TestConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ItemsControl ctrl = parameter as ItemsControl;
Label lbl = ctrl.Items[0] as Label;
var c = lbl.Content;
...
}