ValueConverter Convert Methods are not invoked in WPF - c#

I am having ValueConverter which I am trying to use.
All bindings, including DataGrid content are working correctly, but I can't apply Value converter neither to the DataGridColumns, not to other fields, because it's methods are not invoked at all.
Possible problematic place could be the fact that we are using Reactive UI as MVVM framework, so it can be a possible source of problem. I am setting Items Source of the DataGrid via Reactive UI bindings.
But I have tried to set something like on the view:
public partial class OrderTimeSheetUserControl : OrderTimeSheetBaseUserControl
{
public OrderTimeSheetUserControl()
{
this.DataContext = ViewModel; // Set data context in case it may have helped (but it did not)
InitializeComponent();
}
}
P.S.: I have tried built-in BooleanToVisibilityConverter, as well as writing my converter with inheritance of MarkupExtension class and slightly different method of usage. (IValueConverter with MarkupExtension)
Value Converter below:
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BoolToCollapsedVisibilityValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool == false)
{
throw new NotSupportedException($"Conversion from {value.GetType().Name} is not supported by {nameof(BoolToCollapsedVisibilityValueConverter)}.");
}
var boolValue = (bool) value;
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
And it's usage in the XAML page:
<userControls:OrderTimeSheetBaseUserControl.Resources>
<wpfValueConverters:BoolToCollapsedVisibilityValueConverter x:Key="Conv" />
<DataGrid>
<DataGrid.Columns>
<DataGridComboBoxColumn Visibility="{Binding BackPayCategoryVisible, Converter={StaticResource Conv}}" Header="Backpay Cat."/>
</DataGrid.Columns>
</DataGrid>
Reactive UI Binding of the ItemsSource of the DataGrid in xaml.cs:
this.OneWayBind(ViewModel,
viewModel => viewModel.Employees,
view => view.PersonsInOrderDataGrid.ItemsSource)
.DisposeWith(disposable);
So, all properties are bound correctly, but value converters are not working.

A DataGridColumn doesn't inherit any DataContext by default, so your binding fails and that's why the converter is never invoked. This has nothing to do with ReactiveUI.
You can get the binding to work by using a Freezable as suggested in this blog post:
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
XAML:
<DataGrid.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
...
<DataGridTextColumn Header="Backpay Cat." Binding="{Binding Price}"
Visibility="{Binding Data.BackPayCategoryVisible,
Converter={StaticResource Conv},
Source={StaticResource proxy}}"/>

Related

WPF Treeview use property value as Binding Path

I'm trying to create a Treeview using an ObservableCollection of a custom class called MachineComponentFault, which includes a string property called FaultText, and I'd like to make the text localized.
I'm using WPF Runtime Localization from Codeproject to localize texts at runtime, and it usually works as follows:
<TextBlock Text="{Binding Path=NameInResources, Source={StaticResource Resources}}"/>
The problem is that I can't seem to figure out how to set the value of the property to the path, so that it can retrieve the translation. This is what I managed thus far:
<TreeView Name="myTreeView" VirtualizingPanel.IsVirtualizing="True" ItemsSource="{Binding Faults}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type MassComponents:MachineComponentFault}">
<StackPanel Orientation="Horizontal" >
<TextBlock Name="Text1" Text="{Binding FaultText}"/>
<TextBlock Name="Text2" Text="{Binding Path=FLT_PTC_1, Source={StaticResource Resources}}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
Essentially Text1 shows FLT_PTC_1 at Runtime, while Text2 shows "Motor Overheat", which is the value of FLT_PTC_1 in Resources.resx (which can be translated). The issue is that I can't seem to be able to do what Text2 does using FaultText Property.
Is there a way to do it?
EDIT:
Solved it using mm8 solution, while maintaining the WPF Runtime Localization. The solution isn't pretty at all, since it consists in creating a Binding on a dummy class and then retrieving the binding value as a string, which seems a bit convoluted, but it's the best solution I could find.
public class ResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string resourceName = value as string;
if (!string.IsNullOrEmpty(resourceName)) //look up the resource here:
{
Binding b = new Binding(resourceName); //Create Binding using as Path the value of FaultText
b.Source = CultureResources.ResourceProvider; //Get the resources from WPF Runtime Localization ObjectDataProvider
BindingOperations.SetBinding(_dummy, Dummy.ValueProperty, b); //Set the Binding to the dummy class instance
return _dummy.GetValue(Dummy.ValueProperty); //Retrieve the value of the Binding from the dummy class instance and return it
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
//Initialize Dummy class
private static readonly Dummy _dummy = new Dummy();
//Create a dummy class that accepts the Binding
private class Dummy : DependencyObject
{
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(object), typeof(Dummy), new UIPropertyMetadata(null));
}
}
XAML same as mm8 proposed.
You could bind to the FaultText property and use a converter to look up the resource. Something like this:
public class ResourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string resourceName = value as string;
if (!string.IsNullOrEmpty(resourceName)) //look up the resource here:
return Resource1.ResourceManager.GetString(resourceName);
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<TextBlock Name="Text2">
<TextBlock.Text>
<Binding Path="FaultText">
<Binding.Converter>
<local:ResourceConverter />
</Binding.Converter>
</Binding>
</TextBlock.Text>
</TextBlock>

C# WPF Binding Path Data with Converter

i have a problem converting a string to a icon. The icon Geometry is in a ResourceDictionary. The ValueConverter is not called (i tried to debug in the Convert Method of the Converter). Here is my code:
xaml:
<Window.Resources>
<local:StatusToPathDataConverter x:Key="PathConverter"/>
</Window.Resources>
<Grid>
<Path Width="20"
Height="20"
Stretch="Uniform"
Fill="Black"
Data="{Binding Path=Status,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource PathConverter}}"/>
</Grid>
cs:
public partial class MainWindow :Window {
public MainWindow() {
InitializeComponent();
}
public string Status
{
get { return (string)GetValue(StatusProperty); }
set { SetValue(StatusProperty, value); }
}
public static readonly DependencyProperty StatusProperty =
DependencyProperty.Register("Status", typeof(string), typeof(MainWindow));
}
public class StatusToPathDataConverter :IValueConverter {
private static ResourceDictionary iconDictionary;
public ResourceDictionary IconDictionary
{
get
{
if(iconDictionary == null) {
iconDictionary = new ResourceDictionary();
iconDictionary.Source = new Uri("/WPFBindingTest;component/Resources/IconDictionary.xaml", UriKind.RelativeOrAbsolute);
}
return iconDictionary;
}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var status = (string)value;
if(statinStatus == null)
return null;
switch(status.ToLower()) {
case "test":
return IconDictionary["TestIcon"];
// ...
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
You're not binding to anything. You need to tell the Binding to go to the Window to find the Status property. The recommended way to do that is to use RelativeSource, as below:
<Path
Width="20"
Height="20"
Stretch="Uniform"
Fill="Black"
Data="{Binding Path=Status,
RelativeSource={RelativeSource AncestorType=Window},
Converter={StaticResource PathConverter}}"
/>
As #Clemens notes, UpdateSourceTrigger=PropertyChanged doesn't make any sense on this binding and should not be there. That attribute tells the Binding when it should update the binding's source property. The source property is Window.Status, in this case.
However, the Path.Data property does not update the property it's bound to. A Path displays a Geometry; it doesn't edit a Geometry. UpdateSourceTrigger exists for control properties that update viewmodel properties, like TextBox.Text. That's the most common use for UpdateSourceTrigger=PropertyChanged: By default TextBox.Text updates the source property when the TextBox loses focus, but sometimes you want it to update on each keystroke.
Set the DataContext of the window to itself for the binding to work and the Convert method of the converter to get called:
public MainWindow() {
InitializeComponent();
DataContext = this;
}
If the binding to the source property fails the converter will never be invoked.

How to bind ViewModel Property to the dependency property in the convertor

<Window.Resources>
<local:WeightConverter x:Key="weightConverter" RequiredUnit="{Binding VmProp}" />
<TextBlock Text="{Binding Weight, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource weightConverter}}" />
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
In the MyViewModel I have regular property
private string vmProp;
public string VmProp
{
get
{
return "kg";
}
}
And Convertor class has DependencyProperty is:
public class WeightConverter : DependencyObject, IValueConverter
{
public static readonly DependencyProperty RequiredUnitProperty = DependencyProperty.Register("RequiredUnit", typeof(string), typeof(WeightConverter), null);
public string RequiredUnit
{
get
{
return (string)this.GetValue(RequiredUnitProperty);
}
set
{
this.SetValue(RequiredUnitProperty, value);
}
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double dblValue;
if (double.TryParse(value.ToString(), out dblValue))
{
if (this.RequiredUnit == "kg")
{
return dblValue;
}
else
{
return dblValue * 10;
}
return dblValue;
}
return 0;
}
When I do binding in XAML the code works:
<Window.Resources>
<local:WeightConverter x:Key="weightConverter" RequiredUnit="kg"/>
But when I try to bind it to ViewModelProperty the 'RequiredUnit' object is always null.
How can I bind dependency property to ViewModel property?
The reason its null is because you are trying to bind to the view model property from the resources, but the datacontext is not available to the resource. In your output log, you must be getting a binding expression error. Have a look at the output window.
There are multiple ways to get this working.
One way is to give your window a name like x:Name="MainWindow" and then in your binding do like:
<local:WeightConverter x:Key="weightConverter" RequiredUnit="{Binding DataContext.VmProp, ElementName=MainWindow}" />
Another way would be to do it using Relative Source binding.
Put x:Name="leapold" to your Window/Usercontrol
and make your binding with x:reference
<local:WeightConverter x:Key="weightConverter" RequiredUnit="{Binding DataContext.VmProp, Source={x:Reference leapold}}"/>

Xamarin Forms XAML - Boolean attribute set from XAML

I don't understand something about how to set object attribute for xaml about boolean..
I have a MainPage.xaml like this where I set ProportionalSize to true:
<ContentPage.Resources>
<ResourceDictionary>
<converter:BooleanConverter x:Key="Boolean"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<!-- Background during loading of start -->
<AbsoluteLayout>
<local:CustomImage Source="{extension:ImageResource HomeBG.png}"
ProportionalWidth="100" ProportionalHeight="100" ProportionalSize="{True, Converter={StaticResource Boolean}}"
AbsoluteLayout.LayoutBounds="0.5, 0.5, 1, 1"
AbsoluteLayout.LayoutFlags="All"/>
</AbsoluteLayout>
</ContentPage.Content>
I use a customImage for some reason, this is the class
public class CustomImage : Image
{
private bool _ProportionalSize;
public bool ProportionalSize
{
get { return this._ProportionalSize; }
set
{
this._ProportionalSize = value;
TrySize();
}
}
}
Because neither true nor True works, I made a BooleanConverter
public class BooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value;
}
}
However, it still doesn't work...
Additional information: Position 19:75. MarkupExtension not found for true
ProportionalSize="{True, Converter={StaticResource Boolean}}"
Does I'm doing something wrong?
Just set the value, you don't need to use markup extension syntax (those "{}" brackets) or a converter:
ProportionalSize="True"
If you're not actually binding to a value that will change... Don't use a converter or property. It looks like you just want to set true one time in XAML. You can use the x:Arguments attribute for that.
<x:Arguments>
<x:Boolean>True</x:Boolean>
</x:Arguments>
Bigger example from a DataTrigger.
Usecase - The grid has a different value binded to IsVisible, but we want to override that if the administrator is logged in. So in addition to the regular binding, we can put in a datatrigger that uses the x:Arguments of x:Boolean to set it to true. - Without a converter... without a property.
<Grid.Triggers>
<DataTrigger Binding="{Binding IsAdmin}"
TargetType="{x:Type Grid}"
Value="True">
<Setter Property="IsVisible">
<Setter.Value>
<x:Arguments>
<x:Boolean>True</x:Boolean>
</x:Arguments>
</Setter.Value>
</Setter>
</DataTrigger>
</Grid.Triggers>
Try to use BindableProperty:
public static readonly BindableProperty ProportionalSizeProperty =
BindableProperty.Create(nameof(ProportionalSize),
typeof(bool),
typeof(CustomImage),
default(bool),
propertyChanged: OnProportionalSizeChanged);
public bool ProportionalSize
{
get { return (bool)GetValue(ProportionalSizeProperty); }
set { SetValue(ProportionalSizeProperty, value); }
}
private static void OnProportionalSizeChanged(BindableObject bindable, object oldValue, object newValue)
{
var customImage = bindable as CustomImage;
if (customImage != null)
{
customImage.TrySize();
}
}

My c# ComboBox does not call the getter after selecting an item. Why?

I have an Combobox with SelectedItem. If I select an item my setter does some calculation and perhaps I want to reset the value to the old one. Unfortunatly my view does no refresh.
I have the following ComboBox :
<ComboBox BorderThickness="0" VerticalAlignment="Center" Margin="2,0"
DisplayMemberPath="Name"
ItemsSource="{Binding ItemsVS.View}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" >
</ComboBox>
Here are the properties of my ViewModel:
private CollectionViewSource _itemsVS;
public CollectionViewSource ItemsVS
{
get { return _itemsVS; }
set
{
_itemsVS = value;
if(PropertyChanged!=null)
PropertyChanged(this, new PropertyChangedEventArgs("ItemsVS"));
}
}
private ItemViewModel _selectedItem;
public ItemViewModel SelectedItem
{
get
{
// after setting the old value in the setter the getter is not called
// and the view is not refreshed
return _selectedItem;
}
set
{
var old = _selectedItem;
_selectedItem = value;
// After some logic I want to have the old value!!!!!!!!!!!!!!
_selectedItem = old;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
If you want to make the ComboBox read back the current value after setting the new value, you need to add a "no op" Converter to your Binding that effectively does nothing. It's a useful little trick, as a binding will not normally check to see whether the source value actually applied matches the new value provided by the binding. Adding a converter forces it to check.
public sealed class NoOpConverter : IValueConverter
{
public static readonly NoOpConverter Instance = new NoOpConverter();
public object Convert(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return value;
}
public object ConvertBack(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
return value;
}
}
<ComboBox SelectedItem="{Binding Path=SelectedItem,
Mode=TwoWay,
Converter={x:Static NoOpConverter.Instance}}"
... />

Categories

Resources