If I don't add explicit accessors to a String property, then data binding doesn't work. Why is that?
Here is a simple example where a text box is hooked up to a String property.
MainPage.xaml:
<Grid Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Text="{Binding Message} />
</Grid>
And the code behind:
public String Message;
public MainPage()
{
InitializeComponent();
Message = "Hello World";
DataContext = this;
}
This does not work, the text box is empty.
However, add property accessors;
public String Message { get; set; };
And now it works.
I can't see this explained in MSDN Data Binding. Can someone explain it? Don't properties have an implict set/get accessors? Even so, why can't data binding just access the property?
Thanks,
public String Message;
This is a field, not a property. Adding getters and setters creates an auto property.
Only properties can be bound to.
The { get; set; } is what makes the compiler generate those implicit accessors. If you leave that out, you're not creating a property at all, but a simple field.
I'm not into XAML that much so I can't say for sure, but maybe it just doesn't support binding to plain fields.
public String Message; is a field, not a property.
Databinding only works with properties.
Related
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
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>
I'm trying to create a simple RSS news aggregator. But I've done something wrong with the binding.
The xaml:
<phone:LongListSelector
x:Name="MainLongListSelector"
Margin="0,0,-12,0"
ItemsSource="{Binding Path=News.List}"
SelectionChanged="MainLongListSelector_SelectionChanged">
The code behind:
(mainpage)
public MainPage()
{
InitializeComponent();
// Set the data context of the LongListSelector control
// to the sample data
DataContext = App.ViewModel;
(viewmodel)
public class NewsViewModel : BaseViewModel
{
public NewsRepository News { get; private set; }
}
The NewsRepository is just an model object holding the List<obj> List. Could anyone point me in the direction where thing goes sideways?
The app runs just fine but the single item in the List I've put in manually does not show up in the application :-(
I've set and breakpoint at the assignment of the DataContext to the the viewmodels content its there...
Both NewsViewModel and NewsRepository have to implement INotifyPropertyChanged.
It would be easier if you use ObservableCollection<obj> as a source of your binding instead of custom NewsRepository object.
I'm writing a windows Phone app; and as a good citizen I'm using MVVM pattern :) Being not so expert in MVVM, I faced the following issue that I hope I find solution for here.
I have the following code:
XAML
<Grid x:Name="LayoutRoot" Background="Transparent" DataContext="{Binding MyPOCO}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding IsActive}"/>
</StackPanel>
</Grid>
Code Behind
this.DataContext = new ViewModel();
ViewModel
public class ViewModel : ViewModelBase
{
private POCO myPOCO;
public ViewModel()
{
this.myPOCO = new POCO();
this.ToggleActiveStatusCommand = new RelayCommand(this.ToggleActiveStatus);
}
public POCO MyPOCO
{
get
{
return this.myPOCO;
}
}
public RelayCommand ToggleActiveStatusCommand { get; private set; }
private void ToggleActiveStatus()
{
this.MyPOCO.IsActive = !this.MyPOCO.IsActive;
System.Diagnostics.Debug.WriteLine(this.MyPOCO.IsActive);
this.RaisePropertyChanged("MyPOCO");
}
}
POCO
public class POCO
{
public string Name { get; set; }
public bool IsActive { get; set; }
}
What I'm trying to achieve is to change the TextBlock text as the value of IsActive changes... how to do that? I mean other than exposing the required properties from POCO as properties to ViewModel.
Thanks
I think you will have to implement properties in your ViewModel that call into the POCO properties unless you implement INotifyPropertyChanged in your POCO which then goes against MVVM where your Model should just hold your data and your ViewModel is the glue that links the Model with the View.
Creating properties in your ViewModel may seem like alot of additional work but it does add the benefit of being able to define many ViewModels based on a single Model. So in the specific ViewModel you only expose properties that you need rather than everything the Model can offer you.
Once you have defined your ViewModel properties, you may need to set the Mode of your Binding to TwoWay so that the UI can be updated when the underlying ViewModel properties change.
Let's say that I have a global variable defined in App.xaml.cs as follows:
public static MyClass GlobalInstance = new MyClass()
And then in MainPage.xaml I would like to bind to a property of this class like follows:
<TextBlock Text="{Binding App.GlobalInstance.Property1}" VerticalAlignment="Top" Height="31" HorizontalAlignment="Left" Width="80">
Is there something I am missing here? For some reason it does not appear to be properly bound.
Any advice here would be greatly appreciated.
Thanks!
You need to assign your App to DataContext of the page
First way is do this in page constructor:
public MainPage()
{
InitializeComponent();
DataContext = App.Current;
}
And your binding will be
{Binding GlobalInstance.Property1}
The second way is to make a reference to App class in page resources
Also, edit your field implementation to something like this:
public static MyClass GlobalInstance {get; private set; }
...
GlobalInstance = new MyClass();