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

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.

Related

Binding to interface only if it is the needed type (use fallback otherwise)?

I have the following property in my ViewModel
public IEquipment Equipment
{
get
{
return equipment;
}
set
{
if (equipment != value)
{
equipment = value;
InvokePropertyChanged("Equipment");
}
}
}
This item itself has a bool property, which is bound to an Ellipse in my View, which I want to use as a indicator item:
<Ellipse Width="10" Height="10" Fill="{Binding Equipment.IsAvailable, Converter={StaticResource BoolToColorConverter}, FallbackValue=DarkGray}" Margin="1"/>
The BoolToColorConverter simply converts the color to either green (true) or red (false). During runtime Equipment can be an instance of one of two class types which inherit from IEquipment. Only one of them has the IsAvailable property. In practice this works fine, I get eighter my red or green color...or a gray one, in case the other type of equipment is active.
Problem is, that each time the GUI updates, the following warning gets output:
System.Windows.Data Warning: 40 : BindingExpression path error: 'IsAvailable' property not found on 'object'
How can I avoid this issue? Basically I want to bind to this property only if it is of the correct type.
I can think of two solutions, which I'm not particularly fond of:
Simply add the IsAvailable property to the other type and set it to null (BoolToColorConverter can handle null values and returns dark grey): This might be ok for a simple bool, but in my actual case there are other items, which are quite class specific.
Do the databinding in the code-behind: This might work. Using an event like Loaded on startup to set the binding manually at runtime based on the type. However, this might be troublesome for debugging later, because all other Bindings in the project happen directly in the xaml file. Additionally, Equipment might change during the lifetime of the ViewModel, so I would have to somehow track it.
Xaml doesn't bind to interfaces, it binds to concrete types.
If your types have different properties, then you need different xaml to bind them.
Use DataTemplates to specify different xaml for displaying each type.
If the properties on your derivatives of IEquipment (here Equipment and OtherEquipment as examples) differ a lot and do not share a common interface, they most likely differ in their appearance. In this case you would need different DataTemplates for each type. This is an example for a ContentControl, but it works the same fot ItemsContols with implicit data templates (no x:Key, but a DataType) that are applied automatically.
<ContentControl Content="{Binding Equipment}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:Equipment}">
<Ellipse Width="10" Height="10" Fill="{Binding IsAvailable, Converter={StaticResource BoolToColorConverter}, FallbackValue=DarkGray}" Margin="1"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:OtherEquipment}">
<Ellipse Width="10" Height="10" Fill="DarkGray" Margin="1"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
A workaround for your specific issue could be writing a custom, specialized value converter.
public class EquipmentAvailabilityToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Equipment equipment)
return equipment.IsAvailable ? Brushes.Green : Brushes.Red;
return (Brush)parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
<Ellipse Width="10" Height="10" Fill="{Binding Equipment, Converter={StaticResource EquipmentAvailabilityToColorConverter}, ConverterParameter={x:Static Brushes.DarkGray}}" Margin="1"/>

Xamarin.Forms. XAML Label IsVisible condition is not getting evaluated as expected

In my project I have to select several options on a page.
Every option has a type MyCustomType and has public string Name property declared.
Every option is displayed via label. When I click on the label I display list of options and select it.
As one option is selected, an empty label with placeholder text (like select an item) for another option should appear below the label with the just selected option.
I use separate labels for every option, not a ListView element (customer's requirement for the particular look & feel).
Number of options is limited, let say it equals to four.
In my viewmodel I have declared the list property (it has been initialized in viewmodel constructor):
public List<MyCustomType> AllOptions { get; }
In my XAML page labels are declared as:
<Label Text="{Binding AllOptions[0].Name}" >
<Label Text="{Binding AllOptions[1].Name}" IsVisible="{Binding AllOptions[0], Converter={StaticResource NullToFalseBoolConverter}}">
<Label Text="{Binding AllOptions[2].Name}" IsVisible="{Binding AllOptions[1], Converter={StaticResource NullToFalseBoolConverter}}">
<Label Text="{Binding AllOptions[3].Name}" IsVisible="{Binding AllOptions[2], Converter={StaticResource NullToFalseBoolConverter}}">
Converter NullToFalseBoolConverter looks like that:
public class NullToFalseBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The problem is that IsVisible condition, specified into label declarations does not work.
All labels are displayed.
And breakpoint, set to the first line of Convert method of NullToFalseBoolConverter is not getting reached.
I don't understand why does it happen.
Any ideas?
Instead of trying binding in that matter, consider using either a layout type that supports both an ItemSource and also a data template. So this would be something like a ListView/CollectionView/Stacklayout
So, for example, if you decide to use a StackLayout for example:
<StackLayout
...
BindableLayout.ItemsSource="{Binding AllOptions}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Name}" IsVisible="{Binding ., Converter={StaticResource NullToFalseBoolConverter}}">
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
The beauty of this approach is that since your labels all follow the same approach you can now not only A) write cleaner code, and B) leverage MVVM patterns. Now, of course this does mean that each control gets the converters applied to them as well; however, if still want to not include it for the first element then all we have to do is change your type to include an index property. If you're wondering what the syntax Binding . means, it means that we are just binding the whole object of that collection for that element.
public int Index {get; set;}
Set it where you build that array, and then in the converter all you have to do is:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var element = (MyCustomType) value;
if(element.Index != 0)
return value != null;
else
return true;
}
EDIT
So as an addition to what you want to do in terms of passing the selected item to your command you can do the following. I am assuming that your overall page is a , but the same concept applies to any type of page really.
Set an x:Name property on the , give it any name you want. ex:
<ContentPage
...
x:Name="root">
Define a <GestureRecognizer> on your Label
<Label Text="{Binding Name}" IsVisible="{Binding ., Converter={StaticResource NullToFalseBoolConverter}}"> <Label.GestureRecognizers> <TapGestureRecognizer Command="{Binding BindingContext.YourCommandName, Source={x:Reference root}}" CommandParameter="{Binding .}"/> </Label.GestureRecognizers>
In your view model that is bond to the page, create the following command as follows:
public ICommand YourCommandName => new Command(x =>
YourCustomMethodHere(x));
Finally create the method that handles the object you selected
public void YourCustomMethodHere(MyCustomType type) {}
So what the above XAML code does is that we are binding the command of the Label to the overall parent view model, when an item is inside of a DataTemplate that has been defined by its ItemSource, its view model is actually the model that is being used a data template; that is why we are setting its source VM to be the one for the overall parent. The CommandParameter="{Binding .}" is the same logic as before, we are binding that whole data template item, in this case the MyCustomType that has been rendered for that element. This way each time that label is tapped, we are passing that label and its data to the command that we now defined in the VM.
That's funny, but the following approach fixed my problem.
I've had AllOptions declared as List (did not work), and as ObservableCollection (did not work too).
I should declare my list as array:
public MyCustomType[] AllOptions { get; }
And my labels start displaying properly, one after another is set.
And upon processing its values, if I get null value, that means, that we're reached the end of populated options.

Xamarin.Forms using Resources x:Key not available

I have a Xamarin.Forms application (By the way, I am new to Xamarin). I have quite a few elements on the page. I want to set the color of the button based on a property value in my view model. I have a custom IValueConverter class to convert an integer value to a different color object based on the integer value.
I am not quite sure on how to get this working. I am using Brian Lagunas' prism nuget packages and templates. Here is my sample code as image.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="PlayAlongJ.Views.MainPage"
xmlns:converters="clr-namespace:PlayAlongJ.Converters;assembly=PlayAlongJ"
Title="Play-along with J">
<ContentPage.Resources>
<ResourceDictionary>
<converters:IntToColorConverter x:Name="intToColor"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Margin="15,30">
<StackLayout HorizontalOptions="End" Orientation="Horizontal">
<Label Text="Chest Total" VerticalTextAlignment="Center"/>
<Button x:Name="ChestTotal" Text="{Binding ChestAmount}"
BackgroundColor="Blue"
TextColor="White" WidthRequest="150"></Button>
</StackLayout>
</StackLayout>
</ContentPage.Content>
The first Problem I am facing is that in my page resource dictionary section, I can't get x:Key property for any resource I am trying to use. I only get a x:Name property. For example, if I want to set a Style resource with syntax and I only get x:Name, not x:Key. I am also trying to use my value converter. When I use it as , again I only get x:Name, not x:Key. If I choose to use x:Name in the dictionary resource, I get a compilation error saying that resource requires a x:Key, which obviously I can't use since it is not available to me.
Here is my IValueConverter Implementation. Can someone please show me how to setup the resources and my converter properly? I prefer setting them up in XAML rather than in code. Also, once I can setup the resources without compilation errors, a sample line of code to setup the BackgroundColor of my button to a bound integer property in my viewmodel. If the integer is negative, I want to use Red, otherwise, use some other color using the value converter.
public class IntToColorConverter : IValueConverter
{
public Object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return System.Convert.ToInt32(value) >= 0 ? Color.Blue : Color.Red;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Any help is highly appreciated. Thanks.
You should use x:Key attribute like the error message is telling you. This is one of those cases where IntelliSense in XAML files fails to show every possible option that there is. If it doesn't show up, doesn't mean it's not available when you are working with XAML.
Take a look at the Xamarin documentation on Resource Dictionaries for a full explanation on how to use the x:Key attribute on your resources. The most important part in that article regarding your question is:
Each resource have a key that is specified using the x:Key attribute, which gives it a descriptive key in the ResourceDictionary.
You would then use your converter through the StaticResource markup extension like this (look at the content of the BackgroundColor property):
<Button x:Name="ChestTotal" Text="{Binding ChestAmount}"
BackgroundColor="{Binding ChestAmount, Converter={StaticResource intToColor}}"
TextColor="White" WidthRequest="150"></Button>
Regardless of what it is telling you, you should use x:Key, like this:
<ResourceDictionary>
<converters:IntToColorConverter x:Key="intToColor"/>
</ResourceDictionary>
The IntelliSense can fail at these things sometimes. TO make sure your XAML is in order, you could have a look at XAML Compilation.
It should then be available as a StaticResource and you can use it in your button like this:
<Button x:Name="ChestTotal" Text="{Binding ChestAmount}" BackgroundColor="{Binding ChestAmount, Converter={StaticResource intToColor}}" TextColor="White" WidthRequest="150"></Button>
Also follow this link to the Xamarin documentation for a bit more extensive explanation.

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

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.

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