Multibinding with RelayCommand returns unset values - c#

I have a command that binds to a menu item I have, I would like to pass more than one parameter. I have tried using a converter however it seems to return nothing.
My converter
public class AddConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return values.Clone();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
My View model containing my command
class myViewModel: ViewModelBase {
private RelayCommand testCommand;
public ICommand TestCommand {
get {
if (testCommand == null) {
testCommand = new RelayCommand((param) => test((object[])param));
}
return testCommand ;
}
}
//Only trying to print out one of the params as a test
public void test(object parameter) {
var values = (object[])parameter;
int num1 = Convert.ToInt32((string)values[0]);
MessageBox.Show(num1.ToString());
}
My binding on my menu item
//Using tags as a test
<ContextMenu>
<MenuItem Name="testing" Header="Move to Position 10" Command="{Binding TestCommand}" Tag="7">
<MenuItem.CommandParameter>
<MultiBinding Converter="{StaticResource AddConverter}">
<Binding ElementName="testing" Path="Tag"/>
<Binding ElementName="testing" Path="Tag"/>
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
</ContextMenu>
After debugging, when I open my window containing the menu item, the converter fires off, the values object is null at that point. Then, when I select my menu item and fire off the command, when I get to my execution, the parameter is null. I don't understand why my converter fires off before I even click the menu item, or why the values are null.

Try to replace the ElementName of the bindings with a RelativeSource. This works for me:
<MenuItem Name="testing" Header="Move to Position 10" Command="{Binding TestCommand}" Tag="7">
<MenuItem.CommandParameter>
<MultiBinding Converter="{StaticResource AddConverter}">
<Binding Path="Tag" RelativeSource="{RelativeSource Self}"/>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</MenuItem.CommandParameter>
</MenuItem>
Also note that you should bind to the TestCommand property and not to the testCommand field.

Related

WPF Label content shows DependencyProperty.UnsetValue in design mode

I have an WPF label and I have bound some data into a string using StringFormat from xaml:
<Label Grid.Row="0" Grid.Column="1" Style="{StaticResource MyLblResource}">
<Label.Content>
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding StringFormat="{}({0}) {1}">
<Binding Path="MyDataModel.Id" />
<Binding Path="MyDataModel.Desc" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Label.Content>
</Label>
Above code works fine, no problems but in design time, in xaml view, in the TextBlock content it is shown:
{{DependecyProperty.UnsetValue}} {{DependencyProperty.UnsetValue}}
Why is this being shown instead of being shown as empty? Is there any way to show this as empty?
This should do the trick:
public class StringFormatConverter : MarkupExtension, IMultiValueConverter
{
public string StringFormat { get; set; } = #"({0}) {1}";
public string PlaceHolder { get; set; } = "Empty";
public override object ProvideValue(IServiceProvider serviceProvider) => this;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return string.Format(StringFormat, GetValues(values));
}
private IEnumerable<string> GetValues(object[] values)
{
foreach (var value in values)
yield return value == DependencyProperty.UnsetValue || value == null ? PlaceHolder : value.ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new[] { Binding.DoNothing, Binding.DoNothing };
}
}
Use it like this:
<MultiBinding Converter="{converter:StringFormatConverter PlaceHolder=MyPlaceHolderText}">
<Binding Path="MyDataModel.Id" />
<Binding Path="MyDataModel.Desc" />
</MultiBinding>
Please be aware that you can only set static values in StringFormat and PlaceHolder - because they are no DependencyProperty.

MultiValueConverter works with Color property but not with Background property

I have a MultiValueConverter returning one of two SolidColoBrush passed as values[] elements depending on a bool passed as values[] elment as well.
All values[] elements are DependencyProperty.
The problem is that when I bind a Background property to these SolidColorBrush through this MultiValueConverter i get a cast error.
If I bind the SolidColorBrush.Color property to these SolidColorBrush (which seems an error to me unless there is an implicit conversion somewhere) everything works fine at runtime, but the designer does not show the desired Background, it shows always the background associated with the "false" value of my DependencyProperty even if I set the property to true.
The code is this:
XAML - UserControl
<UserControl.Resources>
<local:State2BackgroundConverter x:Key="state2BackgroundConverter"/>
</UserControl.Resources>
<Grid.Background>
<SolidColorBrush>
<SolidColorBrush.Color>
<MultiBinding Converter="{StaticResource state2BackgroundConverter}">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Path="Status"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Path="BrushOFF"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Path="BrushON"/>
</MultiBinding>
</SolidColorBrush.Color>
</SolidColorBrush>
</Grid.Background> // <--- This works at runtime but not at design time
<Grid.Background>
<MultiBinding Converter="{StaticResource state2BackgroundConverter}">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Path="Status"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Path="BrushOFF"/>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}" Path="BrushON"/>
</MultiBinding>
</Grid.Background> // <--- This generates a cast error
XAML - Window
<LightControls:MyButton Margin="1,0,0,0" Height="80" HorizontalAlignment="Left" Width="80" BrushON="#FF7CFEFF" BrushOFF="#FF0096FF" Status="True"/>
C#
public class Status2ColorConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)values[0] == true)
return ((SolidColorBrush)values[2]);
else
return ((SolidColorBrush)values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return (null);
}
}
private static readonly DependencyProperty StatusProperty =
DependencyProperty.Register("Status",
typeof(bool),
typeof(MyButton),
new PropertyMetadata(tre)
);
public bool Status
{
get { return (bool)this.GetValue(StatusProperty); }
set { this.SetValue(StatusProperty, value); }
}
private static readonly DependencyProperty BrushONProperty = DependencyProperty.Register("BrushON",
typeof(SolidColorBrush),
typeof(MyButton),
new PropertyMetadata(new SolidColorBrush(Colors.Cyan))
);
public SolidColorBrush BrushON
{
get { return (SolidColorBrush)this.GetValue(BrushONProperty); }
set { this.SetValue(BrushONProperty, value); }
}
private static readonly DependencyProperty BrushOFFProperty = DependencyProperty.Register("BrushOFF",
typeof(SolidColorBrush),
typeof(MyButton),
new PropertyMetadata(new SolidColorBrush(Colors.Black))
);
public SolidColorBrush BrushOFF
{
get { return (SolidColorBrush)this.GetValue(BrushOFFProperty); }
set { this.SetValue(BrushOFFProperty, value); }
}
So my questions are:
Why I have to bind SolidColorBrush.Color property even if my BrushON/BrushOFF return SolidColorBrush and not SolidColorBrush.Color?
How I can get this to work at design time? (NOTE: all the other Dependency Property in my project, including those having a custom ValueConverter, not MultiValueConverter, work just fine at design time, even other Background properties)
Thanks in advance!
For those who encounter the same problem, I found an answer to point 2 of my question.
Differently from what happends with IValueConverter, IMultiValueConverter can not evaluate a Dependency Property at design time, even if it has a default value.
Here is the link that gave me the right idea:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/9b902711-2c45-49f9-9478-b36f1e842f0b/imultivalueconverter-error-crashing-visual-studio-at-design-time?forum=wpf
In this article the "as" operator is proposed as solution because it sanifies null variables.
The variable that store the Dependency Property value in my code should not be null at design time (the dependency property has a default value in fact when the same dependency property is used as IValueConverter value everything works fine), so the right thing to do is to prevent this kind of designer misbehaviour by checking if values[] is null and assigning a default return value in that case.
As an example (there could be many smarter ways to obtain the same result):
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string s = values[0] as string;
if (s==null)
{
return (SomeDefaultValue);
}
else
{
Actually do something
}
}

View property - bind twice

Is it possible to bind the same property of the control more than once?
To example:
<Popup IsOpen="{Binding Path=(local:ListViewBehavior.IsColumnHeaderClicked),
RelativeSource={RelativeSource FindAncestor, AncestorType=GridViewColumnHeader}}" ...
As you can see Popup.IsOpen is bound to attached property. I'd like to bind it to ViewModel IsPopupOpened, but have no idea how.
Trying #Arhiman answer without much success:
<Popup.IsOpen>
<MultiBinding Converter="{local:MultiBindingConverter}">
<Binding Path="(local:ListViewBehavior.IsColumnHeaderClicked)"
RelativeSource="{RelativeSource FindAncestor, AncestorType=GridViewColumnHeader}" />
<Binding Path="DataContext.IsPopupId"
RelativeSource="{RelativeSource FindAncestor, AncestorType=UserControl}" />
</MultiBinding>
</Popup.IsOpen>
Naive converter logic:
public class MultiBindingConverter : MarkupExtension, IMultiValueConverter
{
public MultiBindingConverter() { }
public override object ProvideValue(IServiceProvider serviceProvider) => this;
object[] _old;
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (_old == null)
_old = values.ToArray();
// check if one of values is changed - that change is a new value
for (int i = 0; i < values.Length; i++)
if (values[i] != _old[i])
{
_old = values.ToArray();
return values[i];
}
return values[0];
}
// replicate current value
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
Enumerable.Repeat(value, targetTypes.Length).ToArray();
}
You could simply use a MultiBinding with a converter to implement the logic you'd like.
<Popup.IsOpen>
<MultiBinding Converter="{StaticResource openLogicConverter}">
<Binding Path="MyAttachedProperty" ... />
<Binding Path="IsPopupOpened" />
</MultiBinding>
</Popup.IsOpen>
I'd usually put this logic in the ViewModel, but as it is an AttachedProperty, something directly in the View seems more appropriate to me.

Apply conversion to all elements in a ComboBox drop down menu

I am trying to change the text only for contents of all items in a ComboBox based on a specific property in the ViewModel. I’ve created a DataTemplate with the Binding values as SelectedValue and the specific property I want to base the conversion on SomeProperty:
<ComboBox ItemsSource="{Binding Path=ChannelValues}"
SelectedValue="{Binding Path=Channel, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ResourceKey=ChannelNumConverter}">
<Binding Path="SelectedValue"
RelativeSource="{RelativeSource AncestorType={x:Type ComboBox}}" />
<Binding Path="DataContext.SomeProperty"
ElementName="DataContextView" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This appears to work, expect all the values in the drop down get changed to the translated SelectedValue. I’ve tried replacing SelectedValue with Text, but that doesn’t work either. Is there a way to apply this conversion to all values in the drop down (again, only changing the displayed values, not the underlying data)?
Update - ViewModel
// Populate somewhere with values
private ObservableCollection<ushort> mChannelValues = new ObservableCollection<ushort>();
public ObservableCollection<ushort> ChannelValues
{
get
{
return mChannelValues;
}
}
private ushort mChannelNum;
public ushort Channel
{
get
{
return mChannelNum;
}
set
{
if (mChannelNum != value)
{
mChannelNum = value;
OnPropertyChanged(new PropertyChangedEventArgs("Channel"));
}
}
}
private ushort mSomeProperty;
public ushort SomeProperty
{
get
{
return mSomeProperty;
}
set
{
if (mSomeProperty!= value)
{
mSomeProperty= value;
OnPropertyChanged(new PropertyChangedEventArgs("SomeProperty"));
}
}
}
Update 2 - Simple Converter
public object Convert(
object[] values,
Type targetType,
object parameter,
CultureInfo culture)
{
if (targetType != typeof(string))
throw new InvalidOperationException("The target must be a string");
if ((values[0] != null) && (!(values[0] is ushort)))
throw new InvalidOperationException("The channel must be an short");
if ((values[1] != null) && (!(values[1] is ushort)))
throw new InvalidOperationException("The some property must be a ushort");
ushort ushort_val = ushort.Parse((string)values[0]);
ushort ushort_some_property = ushort.Parse((string)values[1]);
switch (ushort_some_property)
{
case 0:
return (ushort_val + 1).ToString();
case 1:
return (ushort_val + 7).ToString();
case 2:
return (ushort_val + 2).ToString();
default:
return ushort_val.ToString();
}
}
Instead of using a MultiBinding, you could use SomeProperty as a ConverterParameter
<TextBlock Text="{Binding Converter={StaticResource ResourceKey=ChannelNumConverter}, ConverterParameter={Binding SomeProperty}}"/>
In converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var someProperty = parameter as SomeType;
...
The problem is in fact that your itemTemplate is applied to ALL! Items in the combobox, whose converter actually processes the currently selected item and someproperty leading to equal values in all items.
The approach at this point is not the problem. You just have to bind the current text's value in the viewmodel instead of the selected value.
This will combine two values from your viewmodel into the result displayed in the textbox without any recursive updates.
Finally figured out how to do this. Below is the xaml to apply the converter to all elements in the drop down list:
<ComboBox ItemsSource="{Binding Path=ChannelValues}"
SelectedValue="{Binding Path=Channel, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ResourceKey=ChannelNumConverter}">
<Binding />
<Binding Path="DataContext.SomeProperty"
RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType=local:DataContextView}" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Complex MultiBinding validation

I have a form that has multiple fields. I have also a "Validate" button that will action a DB input. I would like that button to be activated only if the bare minimum fields are defined by the user.
So far it was quite simple as all the fields were text:
<Button x:Name="Manage" Content="Manage">
<Button.IsEnabled>
<MultiBinding Mode="OneWay" Converter="{StaticResource FieldsFilledinToVisible}">
<Binding ElementName="name1" Path="Text"/>
<Binding ElementName="name2" Path="Text"/>
</MultiBinding>
</Button.IsEnabled>
</Button>
Converter being:
public class AllValuesDefinedConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
bool isEnabled = false;
for (int i = 0; i < values.Length; i++)
{
isEnabled = isEnabled || string.IsNullOrEmpty(values[i].ToString());
}
return !isEnabled;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
}
But would now have to consider extra check boxes, where the condition should be [Any of these checkboxes checked + previous text fields defined --> Activate the validation button]:
<WrapPanel Style="{StaticResource WrapStyle_Inputs}">
<CheckBox Content="Check1" IsChecked="{Binding Checked1, Mode=TwoWay}"/>
<CheckBox Content="Check2" IsChecked="{Binding Checked2, Mode=TwoWay}"/>
<CheckBox Content="Check3" IsChecked="{Binding Checked3, Mode=TwoWay}"/>
</WrapPanel>
Would you know how I could do so?
thank you!
Converters are the wrong tool for the job. You should instead look at validation, commands, and view models. Your view model would implement validation logic and expose a command that your button is bound to. The command would only be executable if your validation logic passes.

Categories

Resources