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);
Related
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.
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
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");
}
}
}
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.
With control level styling I do not need to worry about adding a line of code for every control on every page.
Style Class
public static class Styles
{
#region "Entry"
public static readonly Style Entry_Standard = new Style(typeof(Entry))
{
new Setter {Property = Xamarin.Forms.Entry.BackgroundColorProperty, Value = Color.Red }
};
#endregion
}
Custom Control
public class Custom_Entry : Entry
{
public Custom_Entry()
{
this.Style = Styles.Entry_Standard;
}
}
All of the example documentation from Xamarin though appears to favor setting styles as a property at the page level after object creation either through a page resource or static class reference.
Is there a valid reason for this or is it just that the short examples do not bother to abstract away control styles?