Can't access datacontext in multivalueconverter - wpf

I have a usercontrol which I need to set a specific DataContext on. The usercontrol uses multivalueconverters. However, the binding in multivalueconverter fails to use the datacontext. It works fine for regular value converters. Anyone know what is going on? To clarify, below code is not similar to my real code, it's just a sample reproducing my issue. FOr example, my multivalueconverters take more than one argument.
XAML:
<Window x:Class="MultConvTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:MultConvTest="clr-namespace:MultConvTest"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<MultConvTest:MyValueConverter x:Key="MyValueConverter" />
<MultConvTest:MyMultiConverter x:Key="MyMultiConverter" />
</Window.Resources>
<StackPanel>
<!--This works, output is Formatted: 7-->
<TextBlock
DataContext="{Binding Path=Data}"
Text="{Binding Path=Length, Converter={StaticResource MyValueConverter}}" />
<!--This works, output is Formatted: 7-->
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MyMultiConverter}">
<Binding Path="Data.Length" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<!--This fails, the converter never access the DataContext of the textbox-->
<TextBlock
DataContext="{Binding Path=Data}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource MyMultiConverter}">
<Binding Path="Length" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
namespace MultConvTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = this;
}
public string Data
{
get { return "My Text"; }
}
}
public class MyValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int number = (int)value;
return string.Format("Formatted: {0}", number);
}
public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class MyMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// This will fail when DataContext is set. values[0] is of type Ms.Internal.NamedObject
// with value DependencyProperty.UnsetValue.
int number = (int)values[0];
return string.Format("Formatted: {0}", number);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

You are not going to like this ;-)
Simply put the line DataContext=this before InitializeComponent()
It seems the MultiConverter for the third textbox is triggered before the global DataContext is set, thus in effect producing a null reference error...

Related

Provide value on 'System.Windows.StaticResourceExtension' threw an exception. Converter is defined before it is referenced

I know this looks like a duplicate question but I've tried the solution proposed in similar questions that have been asked.
<UserControl.Resources>
<common:BooleanToVisibilityMultipleConverter x:Key="BoolToVisibilityMultiConverter"/>
</UserControl.Resources>
<Grid Margin="5">
---------------------------------------------------------------------
---------------------------------------------------------------------
<Button Grid.Row="8" Grid.Column="2"
Margin="5"
VerticalContentAlignment="Center" HorizontalAlignment="Right"
Background="{x:Static sds:AppBrushes.Green}"
MinWidth="{StaticResource SdsButtonMinWidth}"
Content="{x:Static res:Resources.UpdatePlantNameButton}"
Command="{Binding CreateDatabaseCommand}">
<Button.Visibility>
<MultiBinding Converter="{StaticResource BoolToVisibilityMultiConverter}">
<Binding Path="IsDatabaseCreated"/>
<Binding Path="IsHistory"/>
</MultiBinding>
</Button.Visibility>
</Button>
</Grid>
</UserControl>
My converter code -
namespace ABB.SDS.Batch.Common
{
public class BooleanToVisibilityMultipleConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.OfType<bool>().All(b => b) ? Visibility.Visible : Visibility.Hidden;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I am referring the converter only in one place and that is after the converter has been defined. I still get this exception
"The method or operation is not implemented.
Resulting in: 'Provide value on 'System.Windows.StaticResourceExtension' threw an exception.' Line number '111' and line position '18'"
What am I missing? Are there any other use cases in which this exception is thrown?

WPF Binding TextBlock to ProgressBar with Percentage

I have a ProgressBar which has a maximum of 10,000 (not 100). I want the binded TextBox to show the percentage of the current Value, not the Value itself. Is it possible to do this in the .xaml code?
The following shows the current Value of my ProgressBar. I want it to show the Value / Maximum.
<ProgressBar x:Name="calculationProgressBar" />
<TextBlock x:Name="calculationProgressText" Text="{Binding ElementName=calculationProgressBar, Path=Value, StringFormat={}{0:0}%}" />
You could setup a simple IMultiValueConverter and pass in the ProgressBars Value and Maximum properties
Example:
Converter
public class ValueToPercentConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double value = System.Convert.ToDouble(values[0]);
double maximum = System.Convert.ToDouble(values[1]);
return (value / maximum) * 100;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="100" Width="100">
<Window.Resources>
<local:ValueToPercentConverter x:Key="ValueToPercentConverter" />
</Window.Resources>
<StackPanel>
<ProgressBar x:Name="calculationProgressBar" Maximum="10000" Value="2566" Height="40"/>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource ValueToPercentConverter}" StringFormat="{}{0}">
<Binding ElementName="calculationProgressBar" Path="Value"/>
<Binding ElementName="calculationProgressBar" Path="Maximum"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Window>
Result:
Since manipulations are not possible in XAML so converters is a approach for the same
I tried to write a simple code for you, using string format for percent notice StringFormat={}{0:P0}
XAML example
<StackPanel>
<StackPanel.Resources>
<l:ScaleConverter x:Key="ScaleConverter"/>
</StackPanel.Resources>
<Slider x:Name="calculationSource" Maximum="10000"/>
<TextBlock Text="{Binding ElementName=calculationSource,
Path=Value, StringFormat={}{0:P0},
Converter={StaticResource ScaleConverter},
ConverterParameter=10000}" />
</StackPanel>
I used slider instead of progressbar for easy demo, you can use any source
specify the max value in converter parameter
and P0 in string format means percentage format with 0 precision eg 0%, you can choose to make it P1 for 1 decimal and so on eg 0.0%
converter class
class ScaleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return System.Convert.ToDouble(value) / System.Convert.ToDouble(parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
converter is pretty simple, divide the value by the defined scale.
I hope you'll find it useful for your issue
Extras
additionally if you prefer to define the max range at a single location you can use resources for the same
<StackPanel>
<StackPanel.Resources>
<l:ScaleConverter x:Key="ScaleConverter"/>
<sys:Double x:Key="maxRange">10000</sys:Double>
</StackPanel.Resources>
<Slider x:Name="calculationSource" Maximum="{StaticResource maxRange}"/>
<TextBlock Text="{Binding ElementName=calculationSource,
Path=Value, StringFormat={}{0:P0},
Converter={StaticResource ScaleConverter},
ConverterParameter={StaticResource maxRange}}" />
</StackPanel>

When is x:Reference in WPF resolved and why does XAML element order affect it?

x:Reference can not be resolved after I re-arrange elements in XAML.
Here I present a working code. Just move the DataGrid element so it comes after the button element and the bindings for the MenuItem in ContextMenu and MultiBinding in Button.IsEnabled become broken. In Button.IsEnabled only MultiBinding is broken. It can be replaced with commented block and x:Reference works in that single binding.
Both throw XamlParseException.
MenuItem gives System.Xaml.XamlObjectWriterException and message talks about unresolved reference.
MultiBinding gives System.Collections.Generic.KeyNotFoundException as inner exception.
So when is that x:Reference actually resolved and why does only some bindings break when referenced element comes after the element that references it?
Here is my XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xxx="clr-namespace:WpfApplication1"
Title="MainWindow" SizeToContent="WidthAndHeight">
<Window.Resources>
<xxx:BoolToVisibleConverter x:Key="boolToVisibleConv"></xxx:BoolToVisibleConverter>
<xxx:NullToFalseConverter x:Key="nullToFalseConv"></xxx:NullToFalseConverter>
<xxx:NullsOrToFalseConverter x:Key="nullsOrToFalseConv"></xxx:NullsOrToFalseConverter>
<ContextMenu x:Key="MyMenu">
<MenuItem
Header="Menuitem enabled when row selected"
IsEnabled="{Binding
Path=SelectedItem,
Source={x:Reference dataGridElement},
Converter={StaticResource nullToFalseConv}}" />
</ContextMenu>
</Window.Resources>
<StackPanel>
<DataGrid
Name="dataGridElement"
IsReadOnly="True" />
<Button
Content="Button"
ContextMenu="{StaticResource MyMenu}"
Visibility="{Binding
Path=IsReadOnly,
Source={x:Reference dataGridElement},
Converter={StaticResource boolToVisibleConv}}">
<Button.IsEnabled>
<!--<Binding
Path="SelectedItem"
Source="{x:Reference dataGridElement}"
Converter="{StaticResource nullToFalseConv}"/>-->
<MultiBinding
Converter="{StaticResource nullsOrToFalseConv}">
<Binding
Path="SelectedItem"
Source="{x:Reference dataGridElement}"/>
<Binding
Path="SelectedItem"
Source="{x:Reference dataGridElement}"/>
</MultiBinding>
</Button.IsEnabled>
</Button>
</StackPanel>
</Window>
Here is my Code behind (without usings):
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class BoolToVisibleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null || (bool)value == false)
return System.Windows.Visibility.Hidden;
else
return System.Windows.Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class NullsOrToFalseConverter : IMultiValueConverter
{
public object Convert(object[] value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
foreach (object val in value)
{
if (val == null)
return false;
}
return true;
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class NullToFalseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value != null);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I suppose it is because your resources(Window.Resources) will be created first, before referenced instance exists. I would try to solve this through DataContext (ViewModel).
<Window.DataContext>
<yourNameSpace:YourViewModel x:Name="VieModName" />
</Window.DataContext>
<MenuItem Header="HeadrTxt" Command="{Binding CommandInViewModelCmd}" DataContext="{x:Reference Name=VieModName}" />
Excerpted from MSDN(http://msdn.microsoft.com/en-us/library/ee795380.aspx).
x:Reference is a construct defined in XAML 2009. In WPF, you can use
XAML 2009 features, but only for XAML that is not WPF markup-compiled.
Markup-compiled XAML and the BAML form of XAML do not currently
support the XAML 2009 language keywords and features.
x:Reference must be avoided in WPF. Because this markup extension is a recent addition to the XAML language (2009). And it is not completely supported in WPF. Use ElementName in your Binding instead of x:Reference.
<Binding Path="SelectedItem"
ElementName="dataGridElement"/>
On MSDN.

How to make Converters with bindable ConverterParameters?

Most converters take no parameters, or one fixed parameter, and are easy to bind to:
<local:MyConverter x:Key="MyConverterInstance" />
<TextBox Text="{Binding Path=MyTime,
Converter={StaticResource MyConverterInstance},
ConverterParameter='yyyy/MM/dd'}" />
But if I want that format to be a dynamic property that the user can change, I can't do something like this, right?:
<TextBox Text="{Binding Path=MyTime,
Converter={StaticResource MyConverterInstance},
ConverterParameter={Binding Path=UserFormat}}" />
So my only option is to define a DependencyProperty on MyConverter for binding. But my converter definition is a StaticResource. I can't go
<local:MyConverter x:Key="MyConverterInstance"
Format="{Binding Path=UserFormat}"/>
because there's no DataContext on StaticResources. How can I set this up?
You cannot bind to a converterparameter but you can use Multibinding instead.
For example: http://www.switchonthecode.com/tutorials/wpf-tutorial-using-multibindings
or How to simply bind this to ConverterParameter?
(Alain) So just to translate that linked answer into something that matches this question:
<TextBlock>
<TextBlock.Resources>
<local:MyConverter x:Key="MyConverterInstance" />
</TextBlock.Resources>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="MyTime" />
<Binding Path="UserFormat" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
DateTime time = (DateTime)values[0];
string format = values[1].ToString();
return time.ToString(format);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}

Switching Binding Sources using a MultiBinding

I have a DataBinding with a MultiBinding of two ObservableCollections, and i want to switch between them on a condition with a MultiConverter.
So the converter gives the right collection, but the binding doesn't seem to be updated.
Any Ideas??
Greets,
Jürgen
This is the converter you need:
public class SwitchCollectionsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool b = (bool)values[2];
if (b)
return values[0];
else
return values[1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
registering the converter:
<local:SwitchCollectionsConverter x:Key="TheConverter" />
usage of the binding:
<ItemsControl>
<ItemsControl.ItemsSource>
<MultiBinding Converter="{StaticResource TheConverter}">
<Binding Path="FirstCollection" />
<Binding Path="SecondCollection" />
<Binding Path="IsFirst" />
</MultiBinding>
</ItemsControl.ItemsSource>
</ItemsControl>
under the assumption that you have a FirstCollection, a SecondCollection, and an IsFirst Properties in the DataContext
Do you need the view to update the source lists?
If so, your binding should be in TwoWay mode:
<TextBox Text="{Binding Source, Mode="TwoWay"}" />

Resources