Custom control in MAUI how it is processing? - custom-controls

Imagine I have custom control - ReqSinglPanel.xaml with BindableProperty "CurrentRequest"
public partial class ReqSinglPanel : Grid
{
public GetRquestsAndUser CurrentRequest
{
get => (GetRquestsAndUser)GetValue(CurrentRequestProperty);
set { SetValue(CurrentRequestProperty, value); }
}
public static readonly BindableProperty CurrentRequestProperty =
BindableProperty.Create(
propertyName: nameof(CurrentRequest),
returnType: typeof(GetRquestsAndUser),
declaringType: typeof(ReqSinglPanel),
defaultValue: null,
defaultBindingMode: BindingMode.OneWay);
public ReqSinglPanel()
{
InitializeComponent();
//string img = CreateCntrImgSources(CurrentRequest.reqCurrIn);
}
}
Now on my CorePage I'm binding object TesOne to this property
<VerticalStackLayout>
<Cntr:ReqSinglPanel HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
CurrentRequest="{Binding TestOne}"/>
</VerticalStackLayout>
Now, I want to do some work with my object INSIDE the ReqSinglPanel
First idea - ReqSinglPanel main constructor. Right?
public ReqSinglPanel()
{
InitializeComponent();
string img = CreateCntrImgSources(CurrentRequest.reqCurrIn);//just to test my assumption
}
Well, no!
{System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.NullReferenceException: Object reference not set to an instance of an object.
So what is the point of having property if I can't modify it inside my custom control to show it my way?
The only possible way that I've found to make any modifications is overriding some methods like
OnSizeAllocated or OnPropertyChanged.
Might I be missing something, could you explain how exactly custom control loading process goes and where I should catch my bound property to torture it?

The bindable properties from XAML haven't been set yet the constructor is called. So you get the default value which is set to be null. A similar issue on Github i have attached below. You could have a look.
As a workaround, define PropertyChanged event handler to Detect property changes.
public static readonly BindableProperty CurrentRequestProperty =
BindableProperty.Create(
propertyName: nameof(CurrentRequest),
...
propertyChanged: OnIsPropertyChanged);
static void OnIsPropertyChanged (BindableObject bindable, object oldValue, object newValue)
{
// Property changed implementation goes here
// could add the logic here
}
For more info, you could refer to
Xamarin Forms Custom Control and Bindable properties not working as expected and Bindable properties.
Hope it works for you.

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.

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

Binding string with Button

I am trying to bind a string to a Button in pure C# (no XAML), but apparently I am doing it wrong, as the result of my code is that the button disappears.
I am defining my property as follows:
public string selectionString { get; set; }
And this is how I am binding the string to the button:
selectionString = "Hello";
selectionButton = new Button
{
TextColor = Color.Black
};
selectionButton.SetBinding(Button.TextProperty, "selectionString");
Children.Add(selectionButton);
I have tried to use BindingMode.TwoWay, but it doesn't work.
Of course, setting the text and removing the binding makes the button appear and work.
My need is just this: the button text should be the selectionString, and if this changes by an external event, so the button's text should change accordingly.
Am I missing something in how the binding works?
Bindings work against public properties on the view's binding context, and respond to INotifyPropertyChanged events firing. Hopefully this demonstrates for you.
public class MyViewModel : INotifyPropertyChanged
{
// Fire RaisePropertyChanged in the setter, I use Fody to weave this in
public string SelectionString {get;set;}
}
public class MyView : Page
{
protected override void OnBindingContextChanged()
{
if (BindingContext is MyViewModel)
{
this.SetBinding(Button.TextProperty, "SelectionString");
}
}
}

Add a custom property/attribute to an existing control

Is it possible to add a custom property to an existing control so that I can bind its value (mvvm)?
For example, I want to add a property as DepartmentId to a Label.
You can either create a custom control extending Label while registering a custom bindable property - or create attached properties.
For e.g., create a custom attached property as following:
public class Ex
{
public static readonly BindableProperty DepartmentIdProperty =
BindableProperty.CreateAttached("DepartmentId", typeof(int), typeof(Ex), defaultValue: -1);
public static int GetDepartmentId(BindableObject view)
{
return (int)view.GetValue(DepartmentIdProperty);
}
public static void SetDepartmentId(BindableObject view, int value)
{
view.SetValue(DepartmentIdProperty, value);
}
}
Sample usage in Label would look like:
<Label x:Name="testLabel" local:Ex.DepartmentId="34" .. />
Or,
<Label x:Name="testLabel" local:Ex.DepartmentId="{Binding DeptId}" .. />
You can access this value using accessor methods defined:
var assignedId = Ex.GetDepartmentId(testLabel);

Xamarin Forms - force a control to refresh value from binding

Given the following ViewModel...
public class NameEntryViewModel
{
public NameEntryViewModel()
{
Branding = new Dictionary<string, string>();
Branding.Add("HeaderLabelText", "Welcome to the app");
}
public Dictionary<string, string> Branding { get; set; }
}
Bound to the page...
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Monaco.Forms.Views.NameEntryPage">
<Label Text="{Binding Branding[HeaderLabelText]}" />
</ContentPage>
When the page comes up the Label will get the text 'Welcome to the app'. This works great and fits into our plan for being able to customize and globalize our app. Then Branding dictionary is statically set in this example, but in the real world it is initialized by data from a service call.
However, should the user want to switch the language to Spanish, we would need each bound label to update to the new value. It's easy enough to reset the Branding dictionary and fill it with Spanish translations, but how can we force the controls to refresh from their bound sources?
I am trying to avoid two-way data binding here b/c we don't want the code overhead of creating a backing property for each Text property of the controls. Hence we are binding to a dictionary of values.
ANSWER
I accepted the answer below, but I didn't use a traditional property setter. Instead when a user wants to toggle a different language, we now have a centralized handler that repopulates our Dictionary and then notifies of the change to the Dictionary. We are using MVVMCross, but you can translate to standard forms...
public MvxCommand CultureCommand
{
get
{
return new MvxCommand(async () =>
{
_brandingService.ToggleCurrentCulture();
await ApplyBranding(); // <-- this call repopulates the Branding property
RaisePropertyChanged(() => Branding);
});
}
}
As #BillReiss mentioned you need to use the OnPropertyChanged event inherit the NameEntryViewModel from this class:
public class BaseViewModel : INotifyPropertyChanged
{
protected BaseViewModel ()
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged ([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler (this, new PropertyChangedEventArgs (propertyName));
}
}
And create a private property that you can assign to you public property something like:
Dictionary<string, string> _branding = new Dictionary<string, string>();
public Dictionary<string, string> Branding
{
get
{
return _branding;
}
set
{
_branding = value;
OnPropertyChanged(nameof(Branding));
}
}
And with this every time you set the Branding property it will let the View know that something changed! Sometimes if you are doing this on a back thread you have to use Device.BeginInvokeOnMainThread()
In your BaseViewModel class is a method:
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null) {}
like:
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
Use SetProperty in the setter-properties of your inherited view-model class.
Then it will work.
INotifyPropertyChanged is already implemeted in BaseViewModel. So nothing to change here.

Resources