Xamarin FreshMvvm Simple Binding - xaml

I have a simple question. How do I access an entry value from a .xaml file in the pagemodel with the FreshMvvm framework.I want the default value that's set in the settings.cs constructor to be what the user inputs in the entry field.
Thanks!
testpage.xaml:
<Label Text="Set Server Address (FreshMvvm Binding):" />
<Entry Text="{Binding Settings.SyncServiceAddress}" Placeholder="Server IP Address" />
<Button Text="Sync Web Service - (FreshMvvm Binding)" Command="{Binding SyncButtonFreshMvvmBinding_Clicked}" />
testpagemodel.cs:
public override void Init(object initData)
{
if (initData != null)
{
Settings = (Settings)initData;
}
else
{
Settings = new Settings();
}
}
public Command SyncButtonFreshMvvmBinding_Clicked
{
get
{
return new Command(async () =>
{
string serverAddress = Settings.SyncServiceAddress;
SyncService.PullNewXMLData(serverAddress);
await CoreMethods.PushPageModel<DashboardPageModel>();
});
}
}
settings.cs:
public class Settings : ObservableObject
{
// Constructor
public Settings()
{
// Default value
SyncServiceAddress = "http://localhost/psm/service.aspx";
}
// Properties
public string SyncServiceAddress { get; set; }
public string UserIDSettings { get; set; }
}

Keeping in mind that the UI only needs to know about a limited set of properties, its ok to create a bindable property in the ViewModel so you're not trying to bind to a nested property.
Create a new string property in the ViewModel called SyncServiceAddress;
public string SyncServiceAddress
{
get{
return Settings.SyncServiceAddress;
}
set{
Settings.SyncServiceAddress = value;
}
}
Then update your XAML to this.
<Entry Text="{Binding SyncServiceAddress}" Placeholder="Server IP Address" />
This should resolve your issue.

Related

Microsoft.AspNetCore.Components.Forms.InputRadioGroup` does not support the type xxx

I want to use radio group in blazor so after implementing edit form and select one of the radio button I got this error :
Microsoft.AspNetCore.Components.Forms.InputRadioGroup`1[EGameCafe.SPA.Models.GameModel] does not support the type 'EGameCafe.SPA.Models.GameModel'.
here is my edit form :
<EditForm Model="ViewModel" OnValidSubmit="HandleCreateGroup">
#if (ViewModel.Games.List.Any())
{
<InputRadioGroup Name="GameSelect" #bind-Value="Gamemodelsample">
#foreach (var game in ViewModel.Games.List)
{
<InputRadio Value="game" />
#game.GameName
<br />
}
</InputRadioGroup>
}
</EditForm>
#code{
public GameModel GameModelSample { get; set; } = new();
}
and GameModel is :
public class GameModel
{
public string GameId { get; set; }
public string GameName { get; set; }
}
The InputRadioGroup, like other Blazor components, supports only a limited amount of types like String or Int32. You had the right idea, but unfortunately, you run into a kind of limitation of Blazor.
You could try to create a wrapper field.
private String _selectedGameId = "<Your Default Id>";
public String SelectedGameId
{
get => _selectedGameId;
set
{
_selectedGameId = value;
// Set the property of the ViewModel used in your Model Property of the EditContext or any other property/field
ViewModel.SelectedGame = ViewModel.Games.List?.FirstOrDefault(x => x.GameId == value);
}
}
Use the property SelectedGameId as the bind value of the InputRadioGroup component.
<InputRadioGroup Name="GameSelect" #bind-Value="SelectedGameId" >
#foreach (var game in ViewModel.Games.List)
{
<InputRadio Value="game.GameId" />
#game.GameName
<br />
}
</InputRadioGroup>
As an alternative, you can create a custom component that inheriting from InputRadioGroup to create a kind of GameBasedInputRadioGroup. If you are interested I can post a sample.
Because in your code #bind-Value="Gamemodelsample",you are trying to bind GameName(string) to Gamemodelsaple(object), which will cause type mismatch problems.
You only need to modify your code to:
#bind-Value="GameModelSample.GameName"

Image source based on properties value in Xamarin.Forms

I've got a custom MyCachedImage that inherits from FFImageLoading.Forms.CachedImage, which is used in a ListView to display images.
The source of this image is composed by 2 properties: a custom object as entity and an integer as size.
Let's say if entity is a "city" object and size is 10 then the image source will be "http://..../city/10/image.png"
Image source must be setted only when both properties are valorized.
So, my answer is, how and when create the source url?
MyCachedImage.vb
public class MyCachedImage : CachedImage
{
public static readonly BindableProperty EntityProperty =
BindableProperty.Create(nameof(Entity), typeof(MyObject), typeof(MyCachedImage));
public MyObject Entity
{
get { return (MyObject)GetValue(EntityProperty); }
set { SetValue(EntityProperty, value); }
}
public static readonly BindableProperty SizeProperty =
BindableProperty.Create(nameof(Size), typeof(int), typeof(MyCachedImage), defaultValue: 0);
public int Size
{
get { return (int)GetValue(SizeProperty); }
set { SetValue(SizeProperty, value); }
}
public MyCachedImage()
{
??? set source here?
}
protected override void OnBindingContextChanged()
{
??? set source here?
}
}
MyPage.xaml
<ListView ....>
....
<control:MyCachedImage Size="10"
Entity="{Binding MyObject}"
WidthRequest="40"
HeightRequest="40" />
....
</ListView>
I was wondering on when create that string and I found the right solution.
The OnBindingContextChanged is called when all properties are setted, so:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (_source == string.Empty)
{
Source = Helpers.ImageHelper.UriFromEntity(Entity, ImageSize);
}
}

Xamarin.Forms change UI language at runtime (XAML)

I am using Strings.resx, Strings.de.resx, etc. to localize Xamarin.Forms app.
I need to be able to change interface language at run time, and it (allmost) works.
Xamarin generates static class Strings in namespace MyProject.Resources from resource files, and I use those values to display strings on UI.
When doing it from code, it works flawlessly:
await DisplayAlert(Strings.lblConfirmDelete, Strings.lblDeleteMessage, Strings.lblOK, Strings.lblCancel));
Problem is - not all attributes defined this way from XAML are updated when I change UI culture during runtime.
Buttons, Labels, Entry properties (Placeholder etc.) change as they should, but PageTitle, Toolbaritems, and some other properties remain in previous language.
I presume that some of these are populated when Page is first created, and are not updated on culture (and UI culture) change.
So, basically, I need a way to combine {DynamicResource ...} with values from resources.
I know that DynamicResource is ment to be used with Resource dictionary, but that is not a good way to store language translations for localization.
I tried
Text="{DynamicResource {x:Static lr:Strings.lblAddNew}}"
also not working.
Is there a way of refreshing page dynamicaly?
I also tried calling
global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(MainListPage));
from Appearing event for that page, but that also does not work.
Any ideas?
Part of XAML file
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyProject.View"
xmlns:rs="clr-namespace:MMPI"
x:Class="MyProject.MainListPage"
xmlns:lr="clr-namespace:MyProject.Resources"
Title="{x:Static lr:Strings.appName}"
>
<ContentPage.ToolbarItems>
<ToolbarItem
Name="New"
Order="Primary"
Priority="0"
Text="{x:Static lr:Strings.lblAddNew}"
Clicked="New_Clicked"
>
When i encountered that challenge in a project I resolved it by using a simple class ResourceLoader and making use of INotifyPropertyChanged.
You can access the Instanceproperty from anywhere and change the culture. All String that are bound to the index would update.
The ResourceManager instance injected into the constructor must be set up appropriately.
public class ResourceLoader : INotifyPropertyChanged
{
private readonly ResourceManager manager;
private CultureInfo cultureInfo;
public ResourceLoader(ResourceManager resourceManager)
{
this.manager = resourceManager;
Instance = this;
this.cultureInfo = CultureInfo.CurrentUICulture;
}
public static ResourceLoader Instance { get; private set; }
public string GetString(string resourceName)
{
string stringRes = this.manager.GetString(resourceName, this.cultureInfo);
return stringRes;
}
public string this[string key] => this.GetString(key);
public void SetCultureInfo(CultureInfo cultureInfo)
{
this.cultureInfo = cultureInfo;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
To display the localized strings in your application you need to bind through the indexer like so:
<Label Text="{Binding [Test], Source={x:Static ResourceLoader.Instance}}" />
Since it is now bound it should update when you call ResourceLoader.SetCultureInfo because the Item[] 'PropertyName' is causing bound controls to re-fetch the values to their bound keys.
Update
I just tested it if i was talking bogus and for some reason the property changed didn't work. I've added a different approach below, which is close to what i'm using in production i urge you to add some kind of weak reference 'caching' instead of the simple list holding all the string resources (otherwise they will be kept forever)
I'm keeping above for reference.
public class ResourceLoader
{
public ResourceLoader(ResourceManager resourceManager)
{
this.manager = resourceManager;
Instance = this;
this.cultureInfo = CultureInfo.CurrentUICulture;
}
private readonly ResourceManager manager;
private CultureInfo cultureInfo;
private readonly List<StringResource> resources = new List<StringResource>();
public static ResourceLoader Instance { get; private set; }
public StringResource this[string key] {
get { return this.GetString(key); }
}
public StringResource GetString(string resourceName)
{
string stringRes = this.manager.GetString(resourceName, this.cultureInfo);
var stringResource = new StringResource(resourceName, stringRes);
this.resources.Add(stringResource);
return stringResource;
}
public void SetCultureInfo(CultureInfo cultureInfo)
{
this.cultureInfo = cultureInfo;
foreach (StringResource stringResource in this.resources) {
stringResource.Value = this.manager.GetString(stringResource.Key, cultureInfo);
}
}
}
StringResource:
public class StringResource : INotifyPropertyChanged
{
public StringResource(string key, string value)
{
this.Key = key;
this.Value = value;
}
private string value;
public string Key { get; }
public string Value {
get { return this.value; }
set {
this.value = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML Binding
<Label Text="{Binding [ResourceKey].Value, Mode=OneWay, Source={x:Static local:ResourceLoader.Instance}}"
/>
Update 2
Came across this link where they implemented it similarly to my first approach. Maybe you can give it a try.
Update 3
Fixed the first approach. Both are working now. What was needed was this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); instead of this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Item[]));
I solved it very similar to #woelliJ . I just wanted to have key as strongly types from static class and binding should be in code behind.
ITranslationService is singleton from static variable. It is very close like #woelliJ .
[ContentProperty("Text")]
public sealed class TranslateExtension : IMarkupExtension<BindingBase>
{
private readonly ITranslationService? _translationService;
public TranslateExtension()
{
_translationService = Mobile.App.TranslationService;
}
public string? Text { get; set; }
public BindingBase ProvideValue(IServiceProvider serviceProvider)
{
var translationItem = _translationService[Text];
var binding = new Binding
{
Mode = BindingMode.OneWay,
Path = $"Value",
Source = translationItem,
};
return binding;
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
}
}
[AddINotifyPropertyChangedInterface]
public class TranslationItem
{
public string? Key { get; set; }
public string? Value { get; set; }
}
Then label would be like this
<Label FontSize="Title" Text="{services:Translate Text={x:Static models:M.AboutTestInfoTitle}}" />

XamlParseException Failed to assign to property. Binding not working with attached property

I want to create custom text box with attached property for Windows Store app. I am following this solution. Now it uses hard coded value as property value but I want to set value using binding, but it's not working. I tried to search a lot but didn't helped me any solution.
The exception details is like this
An exception of type 'Windows.UI.Xaml.Markup.XamlParseException'
occurred in CustomTextBox.exe but was not handled in user code
WinRT information: Failed to assign to property
'CustomTextBox.Input.Type'.
MainPage.xaml
<!-- local:Input.Type="Email" works -->
<!-- local:Input.Type="{Binding SelectedTextboxInputType}" not working -->
<TextBox x:Name="txt" local:Input.Type="{Binding SelectedTextboxInputType}" Height="30" Width="1000" />
<ComboBox x:Name="cmb" ItemsSource="{Binding TextboxInputTypeList}" SelectedItem="{Binding SelectedTextboxInputType}" Height="30" Width="200"
Margin="451,211,715,527" />
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
DataContext = new ViewModel();
}
}
Input.cs
//InputType is enum
public static InputType GetType(DependencyObject obj)
{
return (InputType)obj.GetValue(TypeProperty);
}
public static void SetType(DependencyObject obj, InputType value)
{
obj.SetValue(TypeProperty, value);
}
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(TextBox), new PropertyMetadata(default(InputType), OnTypeChanged));
private static void OnTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is InputType)
{
var textBox = (TextBox)d;
var Type = (InputType)e.NewValue;
if (Type == InputType.Email || Type == InputType.URL)
{
textBox.LostFocus += OnLostFocus;
}
else
{
textBox.TextChanged += OnTextChanged;
}
}
}
ViewModel.cs
public class ViewModel : BindableBase
{
public ViewModel()
{
TextboxInputTypeList = Enum.GetValues(typeof(InputType)).Cast<InputType>();
}
private InputType _SelectedTextboxInputType = InputType.Currency;
public InputType SelectedTextboxInputType
{
get { return _SelectedTextboxInputType; }
set { this.SetProperty(ref this._SelectedTextboxInputType, value); }
}
private IEnumerable<InputType> _TextboxInputTypeList;
public IEnumerable<InputType> TextboxInputTypeList
{
get { return _TextboxInputTypeList; }
set { this.SetProperty(ref this._TextboxInputTypeList, value); }
}
}
This is a pretty common mistake. The problem is, binding targets cannot be CLR properties in XAML. It's just the rules. A binding source can be a CLR property, just fine. The targets simply must be dependency properties.
We all get the error! :)
I describe the whole thing here: http://blogs.msdn.com/b/jerrynixon/archive/2013/07/02/walkthrough-two-way-binding-inside-a-xaml-user-control.aspx
Best of luck.
Incorrect
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(TextBox), new PropertyMetadata(default(InputType), OnTypeChanged));
Correct
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached("Type", typeof(InputType), typeof(Input), new PropertyMetadata(default(InputType), OnTypeChanged));

Cannot show table in WPF from database with use of MVVM pattern

The program works fine and doesn't crashing or something. But data is not showing on the table(datagrid)
Updated version:
View: Userperspective.xaml
I am getting errors in xaml file because of the binding path "Products" is unkown datacontext
<Grid Margin="0,0,0,-20">
<DataGrid Name="Producttable" ItemsSource="{Binding Path=Products}"
HorizontalAlignment="Left" Height="200" Margin="10,44,0,0"
VerticalAlignment="Top" Width="972" />
View: Userperspective.xaml.cs
public partial class Userperspective : Window
{
public Userperspective()
{
InitializeComponent();
DataContext = new ProductViewModel();
}
}
ProductviewModel
private readonly Product _product;
private IBackend _backend;
public ICommand ProductCommand { get; set; }
public IList<Product> Products { get; set; }
public ProductViewModel()
{
_backend = new BackendService();
_product = new Product();
ProductCommand = new ProductCommand(this);
}
public Product Product()
{
return _product;
}
public void LoadProducts()
{
Products = _backend.GetProducts();
RaisePropertyChanged("Products");
}
Productcommand
private readonly ProductViewModel _vm;
public ProductCommand(ProductViewModel vm)
{
this._vm = vm;
}
public void Execute(object parameter)
{
_vm.LoadProducts();
}
BackendService
namespace _blabla
{
class BackendService : IBackend
{
public IList<Product> GetProducts()
{
using (var db = new NORTHWNDEntities())
{
var query = from p in db.Products
select new Product
{
Name = p.ProductName,
};
return query.ToList();
}
}
}
}
Ibackend
namespace _blabla.Commands
{
public interface IBackend
{
IList<Product> GetProducts();
}
}
Seeing as you are new to WPF and MVVM you should break the problem down into something a little more manageable. There is a lot going on in your code; MVVM, commands, database access and some abstraction. Your intentions are sound but it doesn't make solving this problem easy.
With the information you have given I'm not even 100% sure what the problem is but I suspect that it is either the binding or the database access. I will concentrate on demonstrating the binding aspect to you.
Seeing as I don't have access to your database code I have mocked up some classes to help me solve this problem.
Note: The command code is noise so I will remove it from my answer and concentrate on binding to a list of products (you can integrate it with your commanding solution once this is working).
Product
public class Product
{
public string Name { get; set; }
public string Description { get; set; }
public override string ToString()
{
return string.Format("Product: ({0}), {1}", Name, Description);
}
}
BackendService : This basically returns an array of products in lieu of being able to access a database.
class BackendService : IBackend
{
public IList<Product> GetProducts()
{
return new Product[]
{
new Product{ Name = "Laptop", Description = "Dell 17inch laptop" },
new Product{ Name = "Mobile Phone", Description = "iPhone" },
new Product{ Name = "Television", Description = "Samsung 32 inch plasma" },
new Product{ Name = "Car", Description = "Gran Torino" },
new Product{ Name = "Book", Description = "Effective C#" },
};
}
}
I have bound the list of products in the viewModel to a Listbox as I don't have access to the DataGrid but otherwise I have not modified the main window code.
Mainwindow.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox Margin="5"
ItemsSource="{Binding Path=GetProducts}"/>
</Grid>
Mainwindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ProductViewModel();
}
}
Now If I use your viewModel, I get a NullReferenceException which originates from your call to _backend.GetProducts() because you have not instantiated an instance of your BackendService. If I update the constructor like so:
public ProductViewModel()
{
_backend = new BackendService();
_product = new Product();
ProductCommand = new ProductCommand(this);
}
and run the application, the list of products is displayed correctly.
You should be able to integrate the code I have supplied into your project and demonstrate that it is working. When you are happy with this, you should update the BackendService class to call the list of products from your database instead. I would recommend doing this as a matter of course for all bindings that way you know whether it is the binding that isn't working or the database call.
You are trying to execute GetProducts but that is a Property not a method - create a seperate method to load products and
Change your property name to something more meaningful
public IList<Product> Products {get;set;}
Then create a method to load your products
public void LoadProducts()
{
Products = _backend.GetProducts();
//You will need to notify of property change here
OnPropertyChanged("Products");
}
Then bind to Products in your xaml
<Window x:Class="_blabla.View.Userperspective"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="UserPerspective" Height="500" Width="1000">
<Grid Margin="0,0,0,-20">
<DataGrid Name="Producttable" ItemsSource="{Binding Path=Products}"
HorizontalAlignment="Left" Height="200" Margin="10,44,0,0"
VerticalAlignment="Top" Width="972" />
</Grid>
</Window>
Then in your command call LoadProducts
public void Execute(object parameter)
{
_vm.LoadProducts();
}
You will need to implement INotifyPropertyChanged so the UI knows you have changed the Products Property