How to load a view with a converter in another assembly [MAUI.NET] - xaml

I have a converter
using System.Globalization;
namespace WidgetsForRuntimeInjection.Converters
{
public class SampleBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ ... }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{ ... }
}
}
and a view
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewModels="clr-namespace:WidgetsForRuntimeInjection.ViewModels"
xmlns:converters="clr-namespace:WidgetsForRuntimeInjection.Converters;assembly=WidgetsForRuntimeInjection"
x:Class="WidgetsForRuntimeInjection.Views.SampleWidgetsForInjection">
<converters:SampleBooleanConverter x:Key="SampleBooleanConverter"/>
<DataTemplate x:DataType="viewModels:SwitchViewModel" x:Key="SwitchDataTemplate">
<Switch IsToggled="{Binding Value, Converter={StaticResource SampleBooleanConverter}}" IsEnabled="{Binding ReadWrite}"/>
</DataTemplate>
</ResourceDictionary>
both of them are in a single assembly that is build as dll.
Then in another assembly I am loading it and adding to an application resources.
var assembly = Assembly.LoadFile( ... path to dll... );
var resources = assembly.CreateInstance("WidgetsForRuntimeInjection.Views.SampleWidgetsForInjection");
App.Current.Resources.Add("SwitchDataTemplate", (resources as ResourceDictionary)["SwitchDataTemplate"]);
And I got error like that:
Microsoft.Maui.Controls.Xaml.XamlParseException: 'Position 7:6. Type converters:SampleBooleanConverter not found in xmlns clr-namespace:WidgetsForRuntimeInjection.Converters;assembly=WidgetsForRuntimeInjection'
But I can create an instance of this converter a line before (in this second assembly).
var converter = assembly.CreateInstance("WidgetsForRuntimeInjection.Converters.SampleBooleanConverter");
And of course with no converter scenario it all works like a charm.
How to make it work? I tried do Converter as DynamicResource and declaring namespace for converters differently.
UPDATE:
That is kind of weird, because it seems to work after I have added same in alternative way - in xaml.cs file like below:
public partial class SampleWidgetsForInjection : ResourceDictionary
{
public SampleWidgetsForInjection()
{
this.Add("SampleBooleanToColorConverter", new SampleBooleanToColorConverter());
InitializeComponent();
}
}

So it seems this is a workaround / solution:
public partial class SampleWidgetsForInjection : ResourceDictionary
{
public SampleWidgetsForInjection()
{
this.Add("SampleBooleanToColorConverter", new SampleBooleanToColorConverter());
InitializeComponent();
}
}

Related

Xamarin forms URL binding on Image

I have a Image in my xaml in which the source is a URL that Iam binding.The URL from json will be like this : "/images/Uploads/e0111.png". I have the URL in my Common values stored class as CommonValues.URL. How can I add this "CommonValues.URL" before the json at the time of binding? So that the source for Image will be http://example.com//images/Uploads/e0111.png.?
If you need Uri
var myUrl= new Uri(CommonValues.URL + "images/Uploads/e0111.png");
If string than
var myUrl=CommonValues.URL + "images/Uploads/e0111.png";
Or you can do it like this in your ViewModel or Page
public string Url => string.Format("{0}{1}", CommonValues.URL,"/images/Uploads/e0111.png");
Then in XAML:
<Button Text="{Binding Url}"/>
You can use a converter, which will allow you to reuse in all your views/application
public class UrlConverter : IValueConverter
{
#region IValueConverter implementation
public object Convert (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var test = value as string;
if (!string.IsNullOrEmpty(test))
{
return CommonValues.URL + test;
}
return false;
}
public object ConvertBack (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException ();
}
#endregion
}
Then, in your page:
<ContentPage.Resources>
<ResourceDictionary>
<converter:UrlConverter x:Key="UrlConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<Image Source="{Binding YourProperty, Converter={StaticResource UrlConverter}}"/>

How to add : with label where text value is binding dynamically?

How I can add : with string at my label in xaml in xamarin.forms. I have a text coming from app resource file by (i18n:Translate Text=Supplier). Now with this text I also add : after this text. I don't want to add : in app resource with text. I want to do that it on xaml only. I tried with StringFormat but don't know how I can do it.
You can use a Value Converter to change the value on your Binding, but it's tricky because you can't easily add a converter while using i18n:Translate. But I still see three possible solutions to your problem:
1. Property without Value Converter
The easiest way would be to create a Property which gets the translated text and then adds a colon to your text:
ViewModel:
public string Supplier
{
get { return AppResources.Supplier + ":"; }
}
XAML:
<Label Text="{Binding Supplier}"/>
2. Property with Value converter
Another way is to create a property which gets the translated text and then add the colon via a Value Converter:
ViewModel:
public string Supplier
{
get { return AppResources.Supplier; }
}
Converter class:
public class ColonConverter : IValueConverter
{
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.ToString().Remove(value.ToString().Length - 1);
}
}
XAML:
<ContentPage.Resources>
<ResourceDictionary>
<local:ColonConverter x:Key="ColonConverter" />
</ResourceDictionary>
</ContentPage.Resources>
...
<Label Text={Binding Supplier, Converter={StaticResource ColonConverter}}"/>
3. Create your own Translate Extension and Value Converter
I didn't test this, but I found this SO answer which gives an example on how to achieve that. This way you don't need to add properties to your ViewModel, so you only have to adjust your XAML once you set up the Translate Extension and Converter. But it's needs some work to write your own Translate Extension.
Custom TranslateExtension:
[ContentProperty("Text")]
public class TranslateExtension : IMarkupExtension
{
const string ResourceId = "Project.Resources.AppResources";
public string Text { get; set; }
public IValueConverter Converter { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Text == null)
return null;
ResourceManager resourceManager = new ResourceManager(ResourceId, typeof(TranslateExtension).GetTypeInfo().Assembly);
string translatedText = resourceManager.GetString(Text, CultureInfo.CurrentCulture);
if (this.Converter != null)
{
translatedText = Converter.Convert(translatedText, typeof(string), null, CultureInfo.CurrentCulture).ToString() ?? translatedText;
}
return translatedText;
}
}
XAML:
xmlns:strings="clr-namespace:Project.Utils;assembly=Project"
<ContentPage.Resources>
<ResourceDictionary>
<converters:ColonSpaceConverter x:Key="ColonSpaceConverter" />
</ResourceDictionary>
</ContentPage.Resources>
<Label Text="{strings:Translate Money, Converter={StaticResource ColonSpaceConverter}}" />
This can be achieved in many ways, drafting two of them: (Value of Label will be changed on sliding the Slider)
Method 1
<Label Text="Slide to change Value"
VerticalOptions="CenterAndExpand"
HorizontalOptions="CenterAndExpand"
x:Name="lblSliderValue" FontSize="Title" Margin="60"></Label>
<Slider ValueChanged="Slider_ValueChanged"></Slider>
And in CodeBehind file,
private void Slider_ValueChanged(object sender, ValueChangedEventArgs e)
{
lblSliderValue.Text = e.NewValue.ToString("0.00");
lblSliderValue.BackgroundColor = Color.Black;
lblSliderValue.TextColor = Color.White;
}
Method 2 (No need of code in CodeBehind file)
<Label Text="Slide to change Value"
VerticalOptions="Center"
HorizontalOptions="Center"
Text="{Binding Source={x:Reference sldExample}, Path=Value, StringFormat='{0:F2}'}"></Label>
<Slider x:Name="sldExample" BackgroundColor="Yellow" ThumbColor="Violet"></Slider>

Xamarin Forms show/hide option for entry

Currently I am working on Xamarin.Forms and wondering about any possibility to add show/hide option to an entry field?
I have solved a similar issue by using an expand/collapse icon above a number of entry fields.
The show/hide element in XAML
Add a clickable image with fixed size(20x20) referring to embedded resources in the PCL:
<Image Source="{Binding ShowHideIcon, Converter={StaticResource StringToResImageSourceConverter}}" WidthRequest="20" HeightRequest="20"">
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding ShowHideCommand}" />
</Image.GestureRecognizers>
</Image>
The ViewModel processes the command:
Switch the boolean every time the image is touched.
public bool EntryVisible { get; set; }
public Command ShowHideCommand{
get {
return new Command((object o) => {
EntryVisible = !EntryVisible;
if (EntryVisible) {
ShowHideIcon = "ic_collapse";
} else {
ShowHideIcon = "ic_expand";
}
}
}
}
The label and Entry in XAML
Bind the IsVisible attribute of the Label and Entry to the boolean in the ViewModel.
<Label Text="Quantity" IsVisible="{Binding EntryVisible}" />
<Entry Text="{Binding Quantity}" IsVisible="{Binding EntryVisible}" />
For completeness sake, I have used https://developer.xamarin.com/guides/xamarin-forms/working-with/images/#Embedded_Images to store images ic_expand.png and ic_collapse.png in the PCL Resources folder.
A Converter is required to turn a string e.g. "ic_expand" into an image reference that XAML can use.
public class StringToResImageSourceConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
var resString = (string)value;
if (!string.IsNullOrEmpty(resString)) {
return ImageSource.FromResource("ProjectName.Resources." + resString + ".png");
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
Entry entry = new Entry();
// Hide it
entry.IsVisible = false;

Use RelayCommand with not only buttons

I am using MVVM Light in my project and I am wondering if there is any way to use RelayCommand with all controls (ListView or Grid, for example).
Here is my current code:
private void Item_Tapped(object sender, TappedRoutedEventArgs e)
{
var currentItem = (TechItem)GridControl.SelectedItem;
if(currentItem != null)
Frame.Navigate(typeof(TechItem), currentItem);
}
I want to move this code to Model and use RelayCommand, but the ListView, Grid and other controls don't have Command and CommandParameter attributes.
What does MVVM Light offer to do in such cases?
Following on from the link har07 posted this might be of some use to you as I see you mention CommandParameter.
It is possible to send the "Tapped" item in the list to the relay command as a parameter using a custom converter.
<ListView
x:Name="MyListView"
ItemsSource="{Binding MyCollection}"
ItemTemplate="{StaticResource MyTemplate}"
IsItemClickEnabled="True">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ItemClick">
<core:InvokeCommandAction Command="{Binding ViewInMoreDetail}" InputConverter="{StaticResource TapConverter}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</ListView>
Custom converter class
public class TapConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var args = value as ItemClickEventArgs;
if (args != null)
return args.ClickedItem;
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
In your view model you then have a relaycommand.
public RelayCommand<MyObject> MyRelayCommand
{
get;
private set;
}
In your constructor initialise the relay command and the method you want to fire when a tap happens.
MyRelayCommand = new RelayCommand<MyObject>(HandleTap);
This method receives the object that has been tapped in the listview as a parameter.
private void HandleTap(MyObject obj)
{
// obj is the object that was tapped in the listview.
}
Don't forget to add the TapConverter to your App.xaml
<MyConverters:TapConverter x:Key="TapConverter" />

Why I have this error when try to add converter to ResourceDictionary?

I try to add converter to ResourceDictionary because I need to use it, but when only I add declariation an error occur when I run the app.
This is how add converter declaration:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71"
xmlns:converters="clr-namespace:Sample.Utility.Converters">
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
And this is a error I received
{System.Windows.Markup.XamlParseException: The type 'BoolToVisibilityConverter' was not found. [Line: 17 Position: 44]
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
at Sample.App.InitializeComponent()
at Sample.App..ctor()}
I event tried to add the converter to , but the result as the same.
Can any help me with my problem, please ?
this is code of my Converter
public class BoolToVisibilityConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool visible = (bool)value;
return (visible ? Visibility.Visible : Visibility.Collapsed);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Visibility vis = (Visibility)value;
switch (vis)
{
case Visibility.Collapsed:
return false;
case Visibility.Visible:
return true;
}
return null;
}
#endregion
}
To have a resource dictionary available globally in your app, you need to merge it like this in your App.xaml:
<Application x:Class="Sample.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/uri/to/resource_dictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>