A bit confused here, I seem to have followed the steps that would allow me to make use of value converters.
I have my converter defined with a key, as such:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage Title="Article"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:XamarinMobile.Controls;assembly=XamarinMobile"
xmlns:converters="clr-namespace:XamarinMobile.Converters;assembly=XamarinMobile"
x:Class="XamarinMobile.ArticlePage">
<ContentPage.Resources>
<ResourceDictionary>
<converters:FontSizeConverter x:Key="FontSizeMapper"></converters:FontSizeConverter>
</ResourceDictionary>
</ContentPage.Resources>
I then make use of my converter in my XAML, as such:
<ContentView Padding="10,-10,10,0" Grid.Row="2" Grid.Column="0">
<StackLayout>
<Label x:Name="LabelAuthor" FontSize="{Binding 20, Converter={StaticResource FontSizeMapper}, ConverterParameter=20}" />
<Label x:Name="LabelPublishDate" FontSize="{Binding 10, Converter={StaticResource FontSizeMapper}, ConverterParameter=10}"/>
</StackLayout>
</ContentView>
And here is my actual converter code:
namespace XamarinMobile.Converters
{
public class FontSizeConverter : Xamarin.Forms.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(value is double)
{
return App.NormalizeFontSize((double)value);
} else
{
return value;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I then put a break point in my value converter, but it never hits. Is there something obvious that I'm missing here? I'm pretty sure I followed the directions to a tee.
Your breakpoint isn't being hit due to what Gerald Versluis said. Your binding is broken. What your binding is saying is: Bind to the property named "10" on the BindingContext, and use the Converter FontSizeMapper, passing it an extra ConverterParameter of 10. "10" isn't a valid property name, so the binding is breaking. If you look in your logs, you should see a message similar to: "Binding: '10' property not found on ..."
One way to fix it would be to remove the "Path" you're trying to bind to and only make use of the ConverterParameter (assuming you don't need to bind to any real properties):
FontSize="{Binding Converter={StaticResource FontSizeMapper}, ConverterParameter=20}"
Note that you'll need to make use of the parameter in the converter, rather than the value (eg. if (parameter is double)).
If you don't need to bind to any properties, another way to fix it would be to use a custom markup extension instead.
[ContentProperty("FontSize")]
public class FontSizeMapperExtension : IMarkupExtension
{
public double FontSize { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
return App.NormalizeFontSize(FontSize);
}
}
Then you could use it in your XAML like:
FontSize="{converters:FontSizeMapper FontSize=10}
Edit
An example of binding to a property on an object:
public class YourViewModel
{
public double VMFontSize { get; set; }
}
public partial class ArticlePage : ContentPage
{
public ArticlePage()
{
InitializeComponent();
// NOTE: You'd probably get your view-model another way
var viewModel = new YourViewModel { VMFontSize = 10 };
BindingContext = viewModel;
}
}
Now that your view-model is set as the binding context, you can set the binding like:
FontSize="{Binding VMFontSize, Converter={StaticResource FontSizeMapper}}"
What this says is: Bind the FontSize property on the label to the VMFontSize property on the current BindingContext (your view-model), using the converter to map between the view-model's VMFontSize and the Label's FontSize. I left the ConverterParameter off here as it isn't really needed in this example, but you could pass one if you need it.
I would do this a different way, using a custom attached property, see more on attached properties here https://developer.xamarin.com/guides/xamarin-forms/xaml/attached-properties/
Here is a sample for your scenario, first we need to define an attached property, it can be in any class, I called mine FontHelper
namespace App23
{
public static class FontHelper
{
public static readonly BindableProperty FontSizeProperty =
BindableProperty.CreateAttached("FontSize", typeof(double), typeof(FontHelper), 0d, propertyChanging:OnPropertyChanging);
public static bool GetFontSize(BindableObject view)
{
return (bool)view.GetValue(FontSizeProperty);
}
public static void SetFontSize(BindableObject view, bool value)
{
view.SetValue(FontSizeProperty, value);
}
private static void OnPropertyChanging(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is Label)
{
var label = bindable as Label;
double fontSize = (double)newValue;
// normalize your font size here
label.FontSize = fontSize;
}
}
}
}
Then to use it in XAML, it looks like this:
<?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:local="clr-namespace:App23"
x:Class="App23.MainPage">
<Label Text="Welcome to Xamarin Forms!"
VerticalOptions="Center"
HorizontalOptions="Center" local:FontHelper.FontSize="50"/>
</ContentPage>
Related
I'm developing Multiplatform app through Xamarin.
I'm using custom entry in a separate view and I'm using in some pages of my app
This is my simple code for entry
<ContentView
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="prova.MyView">
<ContentView.Content>
<Entry x:Name="MyEntry"
TextChanged="MyEntry_TextChanged"
Margin="100"/>
</ContentView.Content>
and cs file
public partial class MyView : ContentView
{
public MyView()
{
InitializeComponent();
}
void MyEntry_TextChanged(System.Object sender, Xamarin.Forms.TextChangedEventArgs e)
{
}
}
in my pages I insert entry with this simple code
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="prova.MainPage"
xmlns:pages="clr-namespace:prova">
<StackLayout>
<pages:MyView/>
</StackLayout>
I wonder how can I get when MyEntry_TextChanged is fired in my ContentPage?
one solution is to use MessaggingCenter but I wonder if there's a better and more elegant solution
There are two ways I can think of to do this.
1. Inherit from the Entry class as Jason commented.
public class MyView : Entry
{
public MyView()
{
InitializeComponent();
}
}
This will expose the bindable TextChanged property which you can reference in XAML like you want to do.
2. Create the binding yourself
You can create the binding to a custom "TextChanged" property yourself, but this is more complicated and may achieve the same result with extra effort. You will also need to create a bindable "Text" property. The code below is untested, but uses the bindings I found in the Xamarin.Forms InputView class (which is what Entry derives from). This will be along the lines of what you need to do if you do not do it the way of #1. Exposing bindable properties to XAML will look like this:
public class MyView : ContentView
{
public MyView()
{
InitializeComponent();
}
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
public static readonly BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(MyView), defaultValue: "", propertyChanged:
(bindable, oldValue, newValue) => ((MyView)bindable).OnTextChanged((string)oldValue, (string)newValue));
public event EventHandler<TextChangedEventArgs> TextChanged;
protected virtual void OnTextChanged(string oldValue, string newValue)
{
TextChanged?.Invoke(this, new TextChangedEventArgs(oldValue, newValue));
}
}
I hope my more comprehensive answer helps you choose the direction you want to go for this. If you want to learn more about Bindable Properties, check out Edward's link.
I have a Xamarin.Forms solution with Android and iOS projects.
I want the same images to be in both applications.
I know I can add the same image to both projects code seperately, but that doesn't sound very elegant.
I want to have only one image referred to by both projects, from the code in the shared project.
My images are in the shared project, and are Embedded Resource
SharedProject
|
- Resources
|
- icon192.png
My XAML (shared project)
<ContentPage xmlns:myExtensions="clr-namespace:LibraryApp.Extensions;assembly=App">
<Image x:Name="image2"
VerticalOptions="Center" HeightRequest="50" Aspect="AspectFit" />
<Image Source="{myExtensions:ImageResourceExtension Resources.icon192.png}"
VerticalOptions="Center" HeightRequest="50" Aspect="AspectFit" />
</ContentPage>
My code-behind (shared project)
public partial class AboutPage : ContentPage
{
public AboutPage()
{
InitializeComponent();
image2.Source = ImageSource.FromResource("LibraryApp.AndroidClient.Resources.icon192.png");
}
}
The first Image, image2 works perfectly when set from code-behind.
But I don't want to have to deal with images in code-behind, because they are a presentation detail, not implementation.
So my extension method to try and solve this
namespace LibraryApp.Extensions
{
[Preserve(AllMembers = true)]
[ContentProperty(nameof(Source))]
public class ImageResourceExtension : IMarkupExtension
{
public string Source { get; set; }
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Source == null) return null;
// do some work to get the name of the correct assembly/project, here
// var stream = Assembly.GetCallingAssembly().GetManifestResourceStream(Source);
// just for testing, return something we know that works
return ImageSource.FromResource("LibraryApp.AndroidClient.Resources.icon192.png");
}
}
}
This extension method does get called from the other image (with the correct Source parameter).
Even though the code is effectively exactly the same, I cannot get an image using the extension method.
I get a FileImageSourceHandler: Could not find image or image file was invalid.
How would you solve this problem?
Typical, after spending literally a day on this, I figure it out 20m after asking the question...
One solution is to add an IValueConverter
public class ResourceConverter : IValueConverter
{
public Object Convert(Object value, Type targetType, Object parameter, System.Globalization.CultureInfo culture)
{
if (value is not String filename) throw new ArgumentNullException(nameof(value));
var nameOfAssembly = Assembly.GetExecutingAssembly().GetName().Name;
return ImageSource.FromResource($"{nameOfAssembly}.{filename}");
}
public Object ConvertBack(Object value, Type targetType, Object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
And then in the XAML
<ContentPage xmlns:converter="clr-namespace:GPS4Flight.Converter">
<ContentPage.Resources>
<converter:ResourceConverter x:Key="ResourceConverter" />
</ContentPage.Resources>
<Image Source="{Binding Source=Resources.icon192.png, Converter={StaticResource ResourceConverter}}"
VerticalOptions="Center" HeightRequest="50" Aspect="AspectFit" />
</ContentPage>
Notice I needed {Binding Source=[nameOfFile] for it to work.
I have this XAML code:
<?xml version="1.0" encoding="UTF-8"?>
<StackLayout
xmlns ="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:t="clr-namespace:Templates"
x:Class="Japanese..EntryGrid"
x:Name="this" >
<t:HeaderTemplate Text="ABC" />
<Entry HorizontalOptions="FillAndExpand"
VerticalTextAlignment="{Binding EntryTextAlignment, Converter={StaticResource stringToTextAligmentConverter}, Source={x:Reference this}}"
VerticalOptions="FillAndExpand"
Text="{Binding EntryText, Source={x:Reference this}}" />
</StackLayout>
and this C# code:
public partial class EntryGrid : StackLayout
{
public EntryGrid()
{
InitializeComponent();
}
public static readonly BindableProperty
EntryTextAlignmentProperty =
BindableProperty.Create(nameof(EntryText),
typeof(TextAlignment),
typeof(EntryGrid),
TextAlignment.Center,
BindingMode.TwoWay);
public string EntryTextAlignment {
get => (string)GetValue(EntryTextAlignmentProperty);
set => SetValue(EntryTextAlignmentProperty, value); }
}
There's is more code in the template but I just included what was being used for this question.
What I am trying to do is to set the value of the EntryTextAlignment to "Center" like this:
<t:EntryGrid EntryTextAlignment="Start"
HeightRequest="150" />
But it's not taking my request and the text is vertically aligned in the Center.
Does anyone have any idea what I might be doing wrong?
TextAligment Property is Enum, TextAlignment. To bind it correctly you will need to change it from String to TextAlignment. You can also use string, but I think you will have to use converter to handle it. Something like this:
class StringToTextAligmentConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var aligment = (string)value;
switch (aligment)
{
case "Start":
return TextAlignment.Start;
case "Center":
return TextAlignment.Center;
case "End":
return TextAlignment.End;
default:
return TextAlignment.Start;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return TextAlignment.Start;
}
}
xmlns:local="clr-namespace:YourApp.YourNameSpace.Converters"
In your ContentPage declarations in Xaml, then:
<ContentPage.Resources>
<ResourceDictionary>
<local:StringToTextAligmentConverterx:Key="stringToTextAligmentConverter" />
</ResourceDictionary>
</ContentPage.Resources>
Then call it in your Xaml like this:
<Entry VerticalTextAlignment="{Binding EntryTextAlignment, Converter={StaticResource stringToTextAligmentConverter}}"/>
I created a Prism Unity App (from the prism extension pack - really nice), added 2 new views and set up basic navigation between the views. This all worked fine without any hiccups.
What I really want is to use DryIoc (company policy). So I proceeded to remove the unity packages (Unity and Prism.Unity.Forms) and installed the DryIoc Packages (DryIoc and Prism.DryIoc.Forms). Fixed the App.xaml to use the correct namespace (xmlns:prism="clr-namespace:Prism.DryIoc;assembly=Prism.DryIoc.Forms") and alos fixed all the other references to use DryIoc and not the Unity References.
This all compiles and runs without any exceptions. But when I debug it is clear that the AutoWireup does not work as expected (at least as I expect it to). The breakpoint in the ViewModel's constructor (or any other place) is not hit and the Title bindings does not pull through.
Is there a Configuration/Setup or reference that I am missing?
My Code:
App.xaml:
<?xml version="1.0" encoding="utf-8" ?><prism:PrismApplication xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.DryIoc;assembly=Prism.DryIoc.Forms"
x:Class="XamFormsPrism.App"></prism:PrismApplication>
App.xaml.cs:
public partial class App : Prism.DryIoc.PrismApplication
{
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void ConfigureContainer()
{
base.ConfigureContainer();
}
protected override void OnInitialized()
{
this.InitializeComponent();
this.NavigationService.NavigateAsync(NavigationLinks.MainPage);
}
protected override void RegisterTypes()
{
this.Container.RegisterTypeForNavigation<MainPage>();
this.Container.RegisterTypeForNavigation<ViewA>();
this.Container.RegisterTypeForNavigation<ViewB>();
}
}
MainPage.xaml:
<?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:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="XamFormsPrism.Views.MainPage"
Title="MainPage"><StackLayout HorizontalOptions="Center" VerticalOptions="Center">
<Label Text="{Binding Title}" />
<Button Text="{Binding NavigateText}" Command="{Binding NavigateCommand}" /> </StackLayout></ContentPage>
MainPageViewModel.cs:
public class MainPageViewModel : BindableBase, INavigationAware
{
private INavigationService navigationService = null;
private string _title = "Jose Cuervo";
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
public DelegateCommand NavigateCommand { get; private set; }
public MainPageViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
this.NavigateCommand = new DelegateCommand(this.navigate, this.canNavigate);
}
private void navigate()
{
this.navigationService.NavigateAsync("ViewA");
}
private bool canNavigate()
{
return true;
}
public void OnNavigatedFrom(NavigationParameters parameters)
{
}
public void OnNavigatedTo(NavigationParameters parameters)
{
if (parameters.ContainsKey("title"))
Title = (string)parameters["title"] + " and Prism";
}
}
As seen above I use the standard naming convention of PageName & PageNameViewModel with the AutoWireUp set to True.
This all worked fine with Unity but I am missing something in DryIoc...but what escapes me.
I have searched the entire project, there is no reference or trace left of Unity.
Any help is much appreciated.
I finally had time again to have a look at this. If I explicit register the ViewModels in the container everything works fine. So I am guessing that there is either a rule missing for the DryIoC container or when using DryIoC you have to explicitly register the ViewModels.
I'm trying to use a value converter. However, VS is complaining about this XAML:
<Grid x:Name="LayoutRoot" Background="White" Height="Auto" Width="Auto">
<Grid.Resources>
<local:DateFormatter x:Key="FormatConverter" />
</Grid.Resources>
The error:
The type 'local:DateFormatter' was not found. Verify that you are not missing an assembly reference and that all referenced assemblies have been built.
Am I missing an assembly reference, or is my XAML incorrect? I'm trying to follow an example on MSDN.
Update: I added the following attribute to UserControl:
xmlns:local="clr-namespace:MyNamespace"
I also added a DateFormatter class.
Now Intellisense pops up with "local" and "DateFormatter". However, it still gives the same error as above. The error does not occur for other types, such as App.
DateFormatter:
using System;
...
namespace MyNamespace
{
public class DateFormatter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DateTime date = (DateTime) value;
return date.ToShortDateString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
}
Yes - you need to add an xmlns:local="clr-namespace:SilverlightApplication6" to map the namespace and assembly to the XAML namespace local. If you add the converter via Expression Blend, it will put the correct namespace declaration in for you.
As Michael S. Scherotter says, you'll need to add a namespace reference in XAML.
If you're following the MSDN article you'll need to include the following:
xmlns:local="clr-namespace:YOUR-NAMESPACE-HERE"
This is similar to a using statement in C#. It tells XAML where to find your DateFormatter type.
Update:
I'm not sure what's going wrong for you.
The following works for me (applying your DateFormatter code but in a different namespace):
MainPage.xaml
<UserControl x:Class="SilverlightApplication2.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SilverlightApplication2">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.Resources>
<local:DateFormatter x:Key="FormatConverter" />
</Grid.Resources>
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
Text="{Binding FooDate, Converter={StaticResource FormatConverter}}"/>
</Grid>
</UserControl>
MainPage.xaml.cs
using System;
using System.Windows.Controls;
namespace SilverlightApplication2
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
var foo = new Foo { FooDate = DateTime.Now };
DataContext = foo;
}
}
public class Foo
{
public DateTime FooDate { get; set; }
}
}
DateFormmatter.cs
using System;
using System.Windows.Data;
namespace SilverlightApplication2
{
public class DateFormatter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DateTime date = (DateTime)value;
return date.ToShortDateString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}