Xamarin.Forms using Resources x:Key not available - c#

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.

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 designer shows error after program has run and does not display correctly

I have a weird error that the XAML designer displays when hovering over the text that gets the blue underline. It also refuses to show the components correctly in the preview. The text is simply
Object reference not set to an instance of an object.
That looks like a NullReferenceException, but I have no clue where it comes from. It displays correctly in the launched app.
It actually seems to be related to inheriting from List<string> AND exposing a settable property. If I remove either of that it works. But I want both for my converter.
To reproduce it, simply create an empty WPF .NET Framework project, and paste this below the MainWindow.xaml.cs code inside the namespace:
public class BuggyConverter : List<string>, IMultiValueConverter
{
public object Value { get; set; }
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
=> Visibility.Visible;
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) => null;
}
and this into the MainWindow.xaml:
<Window...>
<Window.Resources>
<local:BuggyConverter x:Key="conv" Value="{x:Static Brushes.Yellow}" />
</Window.Resources>
<Grid>
<Border Visibility="{MultiBinding Converter={StaticResource conv}}" />
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Visibility="{MultiBinding Converter={StaticResource conv}}" Width="100" Height="100" Background="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsSource>
<x:Array Type="{x:Type Brush}">
<SolidColorBrush Color="Green" />
<SolidColorBrush Color="Red" />
</x:Array>
</ItemsControl.ItemsSource>
</ItemsControl>
</Grid>
</Window>
Keep MainWindow.xaml open and then launch the app via the Start button. You can see a green and a red square. Since this is hardcoded in the XAML, I'd expect the designer to show just that.
Instead, when you exit the app, the designer shows the color hexcodes instead of the colored square, seemingly because it has problems with the converter setup.
What's the problem?
The code provided by you is working fine at my end. I think this code that you posted is only meant for StackOverflow and your actual code might be different. Anyway, My educated guess for the null reference exception is that, the public Object value{get; set;}
is getting null each time you try to convert something (in your actual use case scenario)
In case the code that you provided is exactly that you are using then just try to build your project to see if that null reference error
goes away or else try below steps
Empty your bin/debug folders from such project
Restart your IDE
Build your projects

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.

Cannot create xmlns for Xamarin.Forms project namespace

I'm encountering the following compilation error when I try to create a new XAML namespace for my current project
Error: Failed to resolve assembly: 'TodoQ.dll, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
I have been using custom controls from both my own and an external project and the compiler has never complained about either. Here is the XAML (the ellipses is some other standard namespaces I decided to omit):
<ContentPage xmlns:util="clr-namespace:TodoQ.Utilities"
xmlns:custom="clr-namespace:TodoQ.Controls"
xmlns:swipecards="clr-namespace:SwipeCards.Controls;assembly=SwipeCards.Controls"
... x:Class="TodoQ.Views.ItemsPage" Title="{Binding Title}" x:Name="BrowseItemsPage">
As you can see, I have two namespaces which refer to my current Xamarin.Forms project. The custom namespace works, but if I try to use util I get the above error. On the other hand, the swipecards namespace works perfectly.
I have tried specifying the assembly, like so xmlns:util="clr-namespace:TodoQ.Utilities;assembly=TodoQ", but it doesn't appear to make a difference.
My only use of util thus far has been trying to add a converter:
<Grid.Resources>
<util:IsZero x:Key="IsZero" />
</Grid.Resources>
Does anybody know what it is I am doing wrong? I'm practically tearing my hair out here. Things are so much simpler with the WPF version of XAML, it all just seems to work there.
EDIT: Here is the Utilities file as requested:
using System;
using System.Globalization;
using Xamarin.Forms;
namespace TodoQ.Utilities
{
public class IsZero : IValueConverter
{
public object Convert(object value,
Type targetType,
object parameter,
CultureInfo language)
{
return ((int)value) == 0;
}
public object ConvertBack(object value,
Type targetType,
object parameter,
CultureInfo language)
{
throw new NotImplementedException();
}
}
}
I honestly think its something else, It might be the way your trying to use it, your code is Correct :
ItemsPage:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:util="clr-namespace:TodoQ.Converters;assembly=TodoQ"
x:Class="TodoQ.Views.ItemsPage">
<StackLayout.Resources>
<ResourceDictionary>
<util:IsZero x:Key="IsZero" />
</ResourceDictionary>
</StackLayout.Resources>
<ContentPage.Content>
<StackLayout>
<Grid HorizontalOptions="CenterAndExpand" >
<Label Text="Hello World" Grid.Row="{Binding UserL , Converter={StaticResource IsZero}}" Grid.Column="0" />
</Grid>
</StackLayout>
</ContentPage.Content>
</ContentPage>
I figured out the problem. Dumb mistake. Typical.
I was missing the <ResourceDictionary> tags, and also had x:Name instead of x:Key in the converter definition.

Loading XAML into a control at runtime and using the control's resources

There's quite a few questions about loading XAML at runtime here,
but as far I can tell this is not a duplicate.
I'm loading XAML into a control at runtime:
<ContentControl Content="{Binding Layout.View, Converter={StaticResource StringToXamlConverter}}"/>
The converter is rather simple:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string xaml;
if (value != null)
{
xaml = value.ToString();
}
else
{
xaml = Settings.Default.DefaultLayoutView;
}
var root = XamlReader.Parse(xaml);
return root;
}
Now, in the XAML that is being loaded, I need to make use of ValueConverters.
The converters are defined as resources of the Window in to which the XAML is loaded. For example:
<c:BooleanToVisibilityValueConverter x:Key="BooleanToVisibilityConverter"/>
Of course, the XamlReader.Parse() method throws an exception if I try to use this resource as at the time of reading it is not available.
Visibility="{Binding Layout.TextItem1.IsEnabled, Converter={StaticResource BooleanToVisibilityConverter}}"
Is there a way to get around this?
Perhaps a way to tell the XamlReader to ignore this?
Or an alternative to ValueConverters that might work in this situation?
Please note, using a DynamicResource does not work either. They cannot be used for ValueConverters.
UPDATED
OK, You are right, dynamic resources will not work. I have come up with two solutions, and the best part is, I have tested and both will actually work.
Choice 1: Define the static resource in the application resources app.xaml
Choice 2: Do not use a static resource for the converter, just create the converter in the xaml. Sorry for not going off the xaml you were using, but you'll get the idea:
<Button>
<Button.Visibility>
<Binding Path="Tag" RelativeSource="{RelativeSource Mode=Self}">
<Binding.Converter>
<controls:BooleanToVisibilityConverter />
</Binding.Converter>
</Binding>
</Button.Visibility>
<Button.Tag>
<sys:Boolean>True</sys:Boolean>
</Button.Tag>
</Button>

Categories