DependencyProperty.UnsetValue appears when using a IMultiValueConverter - c#

I created a simple Converter to concatenate the text of four TextBoxes in my WPF app.
Here is the Converter:
public class FourString:IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return string.Format("{0}{1}{2}{3}", values[0], values[1], values[2], values[3]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new object[] { };
}
}
in Xaml I use this code:
<local:FourString x:Key="converter"/>
<TextBox Grid.ColumnSpan="4" Margin="95,7.5,71.25,3.75" Name="CodeBoatTxt" >
<TextBox.Text>
<MultiBinding Converter="{StaticResource converter}" >
<Binding ElementName="CountryStringaTxt" Path="Text" />
<Binding ElementName="CityStringaTxt" Path="Text" />
<Binding ElementName="ServiceStringaTxt" Path="Text" />
<Binding ElementName="DurationStringaTxt" Path="Text" />
</MultiBinding>
</TextBox.Text>
</TextBox>
When in debug, this error appears in the CodeBoatTxt textbox: "DependecyProperty.UnsetValue".
What is wrong with my converter?

DependencyProperty.UnsetValue is passed into the converter when a Binding is valid, but does not have its value set yet. I would check the Bindings comprising your MultiBinding in isolation and ensure they are correct.

Related

Multibinding with RelayCommand returns unset values

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.

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.

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.

How to get the buttons current enabled/disabled status in WPF multibind 'Convert' method

My program has this kind of a multi bind
<MultiBinding Converter="{StaticResource myConverter}" Mode="OneWay">
<Binding Path="SelectedItems.Count"/>
<Binding Path="EffectiveStyleContext.Selection"/>
</MultiBinding>
IS there anyway to get the current enable disable status in the Convert method
class myConverter: IMultiValueConverter
{
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//I need to get current status here
}
}
You have to pass control itself into a ValueConverter.
Your modified Xaml will be
<MultiBinding Converter="{StaticResource myConverter}" Mode="OneWay">
<Binding RelativeSource="{RelativeSource Self}"/>
<Binding Path="SelectedItems.Count"/>
<Binding Path="EffectiveStyleContext.Selection"/>
</MultiBinding>
Now in your coverter code you will be able to access control.
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var control = values[0] as FrameworkElement;
var value1 = values[1] as int;
// write your logic here.
}
public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return throw new System.NotImplementedException();;
}
}
You can also bind direct to the property itself:
<Binding Path="IsEnabled" RelativeSource="{RelativeSource Self}" />
I would actually argue that this is preferable because the MultiBinding won't get updated when the enable state changes otherwise.

issue with using Style in WPF

Can I write following code using STYLE in xaml?
cmbEnquiry.IsEnabled = (txtQuotationNo.IsEnabled && txtQuotationNo.IsReadOnly == false);
I'm not sure if this will work as is as I'm not in front of an IDE and am trying to code from memory, but if nothing else, it'll serve as an example for MultiBinding.
In your resources:
<local:AndNotConverter x:Key="AndNotConverter" />
<Style ...>
<Setter Property="IsEnabled">
<Setter.Value>
<MultiBinding Converter="{StaticResource AndNotConverter}">
<Binding ElementName="txtQuotationNo" Path="IsEnabled" />
<Binding ElementName="txtQuotationNo" Path="IsReadOnly" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style
In your code-behind:
public class AndNotConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return (bool)values[0] && !((bool)values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Edit:
Just verified the code, and it works as expected.

Categories

Resources