Xamarin, how to create csutom control which accepts content using xaml? - xamarin

I'm trying to make a custom control that provides some standard layout for dialogs, where there's button on the top right (within a grid) which has a property for the command and can be used like a StackLayout with content passed into it.
However, I want to achieve this by specifying all the layout for the control with xaml (not code). I know you can do simple things with ControlTemplates, but I'm trying add a command.
I've tried this a couple of times and given up.
The issues have been setting the content area, where the control can be used with various controls inside and binding the command which is implementing in xaml (with callong BindableProperty within xaml).

In your case you could use Bindable Property to pass value from ViewModel to your custom control .
1. Create a CustomControl with bindable property
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="App29.MyCustomControl"
x:Name="CustomView" // set name of view
>
<ContentView.Content>
<StackLayout>
</StackLayout>
</ContentView.Content>
</ContentView>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace App29
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MyCustomControl : ContentView
{
public MyCustomControl()
{
InitializeComponent();
}
public static readonly BindableProperty ButtonClickCommandProperty =
BindableProperty.Create(nameof(ButtonClickCommand), typeof(ICommand), typeof(MyCustomControl));
public ICommand ButtonClickCommand
{
get => (ICommand)GetValue(ButtonClickCommandProperty);
set => SetValue(ButtonClickCommandProperty, value);
}
public static BindableProperty ButtonClickParameterProperty =
BindableProperty.Create(nameof(ButtonClickParameter), typeof(object), typeof(MyCustomControl));
public object ButtonClickParameter
{
get => (object)GetValue(ButtonClickParameterProperty);
set => SetValue(ButtonClickParameterProperty, value);
}
public static BindableProperty ButtonTitleProperty =
BindableProperty.Create(nameof(ButtonTitle), typeof(object), typeof(MyCustomControl),string.Empty);
public string ButtonTitle
{
get => (string)GetValue(ButtonTitleProperty);
set => SetValue(ButtonTitleProperty, value);
}
public static BindableProperty ChildLayoutProperty =
BindableProperty.Create(nameof(ChildLayout), typeof(View), typeof(MyCustomControl),propertyChanged: OnChildLayoutChanged);
public View ChildLayout
{
get => (View)GetValue(ChildLayoutProperty);
set => SetValue(ChildLayoutProperty, value);
}
static void OnChildLayoutChanged(BindableObject bindable, object oldValue, object newValue)
{
// Property changed implementation goes here
var Custom = bindable as MyCustomControl;
//child is the value that pass from viewmodel
var child = newValue as View;
// do something you want , like add it to stacklayout
var stack = Custom.Content as StackLayout;
stack.Children.Add(child);
}
//...other bindable property
}
}
in ContentPage
Now you can pass value to the custom control from ViewModel or code behind like following
<local:MyCustomControl ButtonClickCommand="{Binding xxx}" ButtonClickParameter="{Binding xxx}" ButtonTitle="{Binding xxx}" >
<local:MyCustomControl.Content>
<Button />
</local:MyCustomControl.Content>
</local:MyCustomControl>
For mode details about Bindable Property you could check https://learn.microsoft.com/en-us/xamarin/xamarin-forms/xaml/bindable-properties

Related

Xamarin access to entry in other view

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.

Compiler error CS0029 at add a new element in app.xaml.cs

I have created a new element and app.xaml doesn't want to initialize it.
In my App.cs file I'm trying to set my TestPage(I've created TestPage by Add->New Item-> BlankPage) as Main Page but i'm getting this error "Severity Code Description Project File Line Suppression State Error CS0029 Cannot implicitly convert type 'smiles.Gridtesst' to 'Xamarin.Forms.Page'"
https://i.stack.imgur.com/fK8qg.png
P.S code
.xaml
<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="smiles.Gridtesst">
<ContentView.Content>
<StackLayout>
<Label Text="Hello Xamarin.Forms!" />
</StackLayout>
</ContentView.Content>
</ContentView>
app file
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace smiles
{
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new Gridtesst(); // error
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}
cs file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace smiles
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Gridtesst : ContentView
{
public Gridtesst()
{
InitializeComponent();
}
}
}
If you are adding a new page/element then you need to choose Content Page (not content View or another).

Dynamic resx translation in Xamarin Forms using MVVM

We have an app written with Pages and no pattern and I want to re-write it using MVVM. Currently we use a Picker for language selection and when the culture changes we set all label.Text controls again in order to redraw them in the new language.
I re-wrote the same page using MVVM and now SelectedItem in the Picker is bound to a Language object. In the setter for SelectedItem I also change the culture of my resx (AppResources.Culture) but the UI bound to it (e.g. Text="{x:Static resources:AppResources.Title) doesn't change language.
Full code in my SelectedItem setter:
SetProperty(ref selectedLanguage, value);
AppResources.Culture = value.Culture;
cultureManager.SetLocale(value.Culture);
How should I update all the Text of my UI? Is there any clean way to do something like this, it seems like a basic translation need... or it wasn't meant to be done, especially not without closing the view/app?
The approaches I found for localization using IMarkupExtension and this thread on Xamarin forums which in the end effectively re-creates the page...
My goal is to ideally reload text without having to re-create the view/close the app, using MVVM and clean code. I have about 10 views so it has to be something reusable.
Create you RESX Resources first. I use en, nl, fr for example.
Create the view model to binding the LocalizedResources.
public class ViewModelBase : INotifyPropertyChanged
{
public LocalizedResources Resources
{
get;
private set;
}
public ViewModelBase()
{
Resources = new LocalizedResources(typeof(LocalizationDemoResources), App.CurrentLanguage);
}
public void OnPropertyChanged([CallerMemberName]string property = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
}
In SettingsPage, use a picker to choose the language.
<StackLayout>
<Label Text="{Binding Resources[PickLng]}" />
<Picker ItemsSource="{Binding Languages}" SelectedItem="{Binding SelectedLanguage, Mode=TwoWay}" />
</StackLayout>
View model of SettingsPage.
public class SettingsViewModel : ViewModelBase
{
public List<string> Languages { get; set; } = new List<string>()
{
"EN",
"NL",
"FR"
};
private string _SelectedLanguage;
public string SelectedLanguage
{
get { return _SelectedLanguage; }
set
{
_SelectedLanguage = value;
SetLanguage();
}
}
public SettingsViewModel()
{
_SelectedLanguage = App.CurrentLanguage;
}
private void SetLanguage()
{
App.CurrentLanguage = SelectedLanguage;
MessagingCenter.Send<object, CultureChangedMessage>(this,
string.Empty, new CultureChangedMessage(SelectedLanguage));
}
}
Do not forget to binding the context.
I have upload on GitHub, you could download from DynamicallyBindingRESXResources folder on my GitHub for reference.
https://github.com/WendyZang/Test.git

New Bindable Picker can't set default item from viewmodel

Trying to get the new bindable picker to work in Xamarin Forms version 2.3.4.270 with Visual Studio 2017.
I can get the view to load in a list from the view model so when you click the picker, it does show the list of items. I want to set a default item in the picker on screen load though and nothing I have tried is working.
View:
<?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:BindablePickerTest"
x:Class="BindablePickerTest.MainPage">
<StackLayout>
<Label Text="Welcome to Xamarin Forms!"
VerticalOptions="Center"
HorizontalOptions="Center" />
<Picker Title="Test Picker" ItemsSource="{Binding Names}" SelectedItem="{Binding Name, Mode=TwoWay}" />
</StackLayout>
</ContentPage>
View Code Behind: (the view model is in the code behind for simplicity)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace BindablePickerTest
{
public partial class MainPage : ContentPage
{
MainPageViewModel vm;
public MainPage()
{
InitializeComponent();
this.BindingContext = vm = new MainPageViewModel();
}
}
public class MainPageViewModel : INotifyPropertyChanged
{
List<string> names = new List<string>
{
"Doug",
"Fred",
"Steve",
"Tom",
"Victor"
};
public List<string> Names => names;
public string Name = "Steve";
public event PropertyChangedEventHandler PropertyChanged;
}
}
When this runs, the picker is still empty - "Steve" is not selected. When clicking the picker, the list shows correctly and I can choose any name, but it will not set a default selection.
Any help is appreciated!
Name is a field right now. For Binding to work - it needs to be converted to property.
public string Name => "Steve";

Value Converter not working in Xamarin

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>

Resources