Design view shows `System.Object` in TextBlock when applying a converter to a Number in Blend - c#

In Microsoft Blend for Visual Studio (Express 2013 and Community 2015 RC), I have created some sample data with a collection of items consisting of a number and a string.
The number amount is bound to a TextBlock:
<TextBlock Text="{Binding amount}" />
The numbers displays fine, except I want them formatted to a string which displays 2 decimals. Since StringFormat is not available for Universal Apps, I tried adding a converter which attempts to achieve the same thing:
public class StringFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return string.Format(parameter as string, value);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return null;
}
}
I add a resource for it:
<Page.Resources>
<ResourceDictionary>
<local:StringFormatConverter x:Name="StringFormat"/>
</ResourceDictionary>
</Page.Resources>
I adjust my TextBlock's binding:
<TextBlock Text="{
Binding amount,
Converter={StaticResource StringFormat},
ConverterParameter='{}{0:f2}'}" />
But when I do this, the view panel displays System.Object for each amount.
Even when I add a converter that does nothing and just returns the value or the toString() of the value, I get the same result! This only happens with the sample data type Number.
How can I format the binding to a number from a sample data set in Blend?

Not sure how helpful this is going to be after all these years, but why don't you do it in code behind? You just have to update the "Text" property of the TextBlock after the necessary conversion is done.

Related

How to set a xaml elements IsVisible property to true if a bound boolean property is false?

I'm wondering if or how I can set the IsVisible property of a xaml element to true if the boolean value of a bound property is false?
Depending on the value of a boolean property I want to conditionally render different elements in the view.
This is my code:
<ContentPage ...
x:Name="page">
<ListView BindingContext="{x:Reference page}" ItemSource="digitalInputs">
<ListView.ItemTemplate>
<DataTemplate x:DataType="services:DigitalInput">
<ViewCell>
<HorizontalStackLayout>
<!-- This seems to be working. Render a green ball, if boolean value is true -->
<Image Source="ballgreen" IsVisible="{Binding value}"/>
<!-- Doesn't work. I want to render a red ball if the boolean value is false. -->
<Image Source="ballred" IsVisible="{Binding !value}"/>
<Label Text="{Binding valueText}" />
</HorizontalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
I have thought about adding another boolean property to the DigitalInput class and set it's value to the opposite of value, but on the other hand I don't want to touch that class as it models 1:1 how I retrieve data from a web-service.
I have also thought about adding a converter, but I don't know how to do it in regards of BindingContext. I hope someone can help and shed some light on this issue for me.
Kind regards.
There is multiple ways to do this as you already mention yourself. The easiest being: add an inverted property to your model. Not the most elegant option.
In XAML itself you can't have any logic, so the ! operator won't work. A converter is indeed the way to go.
Implementing a InverseBoolConverter
To implement a IValueConverter which is the thing you need in this case, create a class that implements this interface. When you do, there is a couple of methods that you need to implement. In this case those are pretty straight-forward as it just inverts the boolean value.
However, since it's XAML and all based on strings, to make it really rock solid you might want to take into account some error handling. In this case, I'm going to assume the happy path. Find a sample implementation below:
public class InverseBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !((bool)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
// Converting back rarely happens, a lot of the converters will throw an exception
//throw new NotImplementedException();
}
}
This code is taken from this SO question.
To now use it in your XAML, do this: <Image Source="ballred" IsVisible="{Binding value, Converter={converters:InverseBoolConverter}}"/>
And you'll need to add a xmlns entry to the root of your page like so: xmlns:converters="clr-namespace:YourProject.Namespace". Make sure that YourProject.Namespace matches the namespace of the InverseBoolConverter above.
Using the .NET MAUI Community Toolkit
The good news is, you don't need to do any of this! The .NET MAUI Community Toolkit has this built-in. You can just install the NuGet package, consume this converter and you're on your way.
Find the documentation for that on this page.

xaml - how to show display value that is different from its property value without using KeyValuePair

I have a ComboBox that grabs its data from a List of strings.
Styles.Add("bold");
Styles.Add("italic");
Styles.Add("underline");
Styles.Add("");
When shown in the ComboBox, I want it to actually display: { "bold", "italic", "underline", "[Discard style]" }, respectively. It's important that I'm able to change that empty string value to display "[Discard style]" but also retain the empty string value when the SelectedChanged event fires.
Is there some way to do this (without changing the underlying data structure to support a KeyValuePair)?
This is what I originally started with:
<ComboBox ItemsSource="{Binding Styles, Mode=OneWay}" SelectedItem="{Binding SelectedStyle}" SelectionChanged="StyleComboBox_SelectionChanged" />
Since then, I've tried adding and doing many variations/combinations of
<ComboBox ItemsSource="{Binding Style, Mode=OneWay}" SelectedItem="{Binding SelectedStyle}" DisplayMemberPath="{Binding SelectedStyle, Converter={StaticResource StyleConverter}}" SelectionChanged="StyleComboBox_SelectionChanged" Height="22" Width="175" />
with SelectedValuePath={Binding SelectedStyle}, where StyleConverter is as follows:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string selectedStyle = value as string;
if (string.IsNullOrEmpty(selectedStyle))
return "[Discard style]";
return value;
}
I've mostly encountered the scenario where the display value is correct, but the underlying value is not - it's somehow always changed to "[Discard style]" as well. Do I maybe need two converters somehow? One just for displaying in the ComboBox and another just for selecting the item?
Another important note is that "[Discard style]" will be translated in the UI based on the operating system. So I can't simply just compare the string to see if it matches and turn it back into an empty string.
There are actually quite a few different ways of achieving this, but for something this simple I'd probably just use a DataTemplate with a DataTrigger to change the text when it's an empty string:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
<ComboBox ItemsSource="{Binding Styles, Mode=OneWay}" SelectedItem="{Binding SelectedStyle}">
<ComboBox.Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding}" />
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="">
<Setter Property="Text" Value="[Discard style]" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ComboBox.Resources>
</ComboBox>
You will, however, need to use a converter if you want to handle null in your string list. In that case ignore the code above and apply the converter to the ItemsSource binding:
<Window.Resources>
<conv:StringConverter x:Key="StringConverter" />
</Window.Resources>
<StackPanel Orientation="Vertical">
<ComboBox ItemsSource="{Binding Styles, Mode=OneWay, Converter={StaticResource StringConverter}}" SelectedItem="{Binding SelectedStyle}" />
</StackPanel>
And then your converter would just look something like this:
public class StringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value as IEnumerable<string>).Select(s => String.IsNullOrEmpty(s) ? "[Discard style]" : s);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
UPDATE: Oh ok, I see what you're trying to do. In that case yes, you need to use two converters....one to filter the list of strings that the view model is passing to the view, and then a second to change the value being propagated back again.
Before I go any further I should point out that what you're trying to do is a really, really bad idea. The whole point of MVVM is that logic is done in the view model. What you're effectively trying to do is implement logic in the view layer, and that's completely against the whole MVVM/data-binding paradigm. The whole purpose of the view model is to prepare data in a format that the view can readily consume with as few changes as possible; the minute you find yourself putting logic in the view (including converters, which are also part of that layer) it's a very strong sign that your view model is not doing its job properly.
To answer your question though, you would need a converter in your SelectedStyleBinding in addition to the one I posted above. You would now also be forced to make that binding one-way-to-source, which means you lose the ability to programatically control the currently selected item:
<ComboBox ItemsSource="{Binding Styles, Mode=OneWay, Converter={StaticResource StringConverter}}" SelectedItem="{Binding SelectedStyle, Converter={StaticResource SingleStringConverter}, Mode=OneWayToSource}" />
And the second converter:
public class SingleStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value?.ToString() == "[Discard style]") ? "" : value?.ToString();
}
}
If you need to retain programatic item selection then your only other option is to bind to SelectedIndex instead of SelectedItem, your view model would then have to be responsible for looking the string up in the source list.
But again, all of this is very bad code. The correct thing to do here is to modify your source list to be a custom view model class or something else your view can more readily work with.

UWP/XAML How to Change Individual Items Within a Data Binding

Say for example I data bind a collection of Book objects to a ListView. The ListView contains a TextBlock of "Book.Title" so the UI will show a list of book titles in the collection. But what if, for certain individual Books in the collection, the boolean property "Book.IsCheckedOut" is true, and I want those specific books to be displayed in boldface (or italic or a different color or whatever.) How can specific elements in a bound collection have unique properties assigned to them?
use a converter, like this one:
public class BoolToFontWeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var currentValue = (bool);
return currentValue ? FontWeights.Bold : FontWeights.Normal;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
then bind it in the xaml like this:
<TextBlock FontWeight="{Binding IsCheckedOut, Converter={StaticResource BoolToFontWeightConverter}}" />
and you'll need to add it as a resource in the page where it's used, or in the app xaml
The right way to do this is implementing the ItemTemplateSelector of your ListView, you can check my answer in another question: Multiple format types listview in C# and XAML.
To implement this, you need:
Design two DataTemplate for your ListView, one in boldface, the other one not.
Implement the class for DataTemplateSelector.
Using this DataTemplateSelector in your ListView.
Add data to your collection and set this collection as ItemsSource of your ListView.

Run-time culture change and IValueConverter in a binding

The application I'm working on has an option to switch the interface language. This successfully changes most of the UI itself which uses, for example,
<Run Text="{Binding Resources.CreditsTitle, Source={x:Static models:ResourceService.Current}, Mode=OneWay}" />
Aside from static UI elements there are some displaying a few models' properties; for most of them no localisation is necessary, save for just one (for now). The models come from a different project within the solution, and are read-only. The one property that needs to be displayed in its localised form is represented by its ID in the model, and I wrote a converter for it that returns a Resource string.
public class RankIDToRankNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
var ID = (int)value;
switch (ID)
{
case 1:
return Resources.Rank_Expert;
case 2:
return Resources.Rank_Intermediate;
And so on.
In the XAML document it gets bound as
<Run Text="{Binding Model.RankID, Converter={StaticResource RankIDToRankNameConverterKey}, Mode=OneWay}"
Style="{DynamicResource PickupTextElementStyleKey}"
FontSize="14" />
…and it works, but just once, when the UI element gets drawn for the first time (obviously it also works when the RankID property changes but that happens extremely rarely). It doesn't change when culture is changed. I'm guessing it's because the property changed event does not fire, so WPF sees no reason to update it.
What's the “proper” way to get it to update when culture is changed?
I have solved this issue by adding a new method to the underlying model. Since I'm using Livet and models are NotificationObject descendants, I added a new public method Update() to the model in question with
this.RaisePropertyChanged("changed_property_name_here");
and adding
try
{
InstanceOfModelStorage.UnderlyingModel.Update();
}
catch (NullReferenceException e) { }
to the culture change handler.

IValueConverter not getting invoked in some scenarios

I am using a collection of texts fetched from a web service, which should be used for a variety of controls.
The easiest and most dynamic way to do this, in my opinion, is to use an IValueConverter to get the given text as follows:
public class StaticTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null && parameter is string)
{
return App.StaticTexts.Items.SingleOrDefault(t => t.Name.Equals(parameter)).Content;
}
return null;
}
}
And then in the XAML I give the ID of the text ('Name') to the converter:
<phone:PhoneApplicationPage.Resources>
<Helpers:StaticTextConverter x:Name="TextConverter" />
</phone:PhoneApplicationPage.Resources>
<TextBlock Text="{Binding Converter={StaticResource TextConverter}, ConverterParameter=M62}" />
Then to change the text of some control, all that has to be done is to either change the ID in the parameter or change the text itself from some web interface.
My problem is
That the value converter only gets invoked when in some sort of DataTemplate context where the ItemSource has been set, as if the Binding property only works there.
Whenever I use this method anywhere else, the value converter is simply not invoked.
Does anyone have an idea of what I might be doing wrong?
Set DataContext="object" for your textblocks where the convertet is not working and the value converter will be invoked.
This workaround will do the trick in your scenario.
add source for the binding
use something like that
Text="{Binding Converter={StaticResource LocalizedStringsConventer} ,ConverterParameter=Wrong, Source=NULL}"

Categories