I'm developing my first app and I'm trying to make it multilanguage.
Using AppHub example and some other link I created my resource files, fixed binding strings on my components and set a settings page.
First problem I had was that menu items and appbar buttons couldn't use localization strings (project complained when launched) so I have:
TextBlocks and other components binded with localized strings
Appbar buttons and items localized manually with a procedure loading localized strings
Now that I have my settings page, one item user can change is language.
Well, correct CultureInfo is selected according to user selection and then I use
Thread.CurrentThread.CurrentUICulture = Settings.Language;
When I press back button and return to main page, appbar items are localized correctly, while everything else is not.
The only workaround (that I really don't like, it's just to understand) is this:
public MainPage()
{
Thread.CurrentThread.CurrentUICulture = Settings.Language;
InitializeComponent();
// Everything else I need here
}
so I have to set language before components are created to make it work.
What's wrong? Which is the correct way to make a page refresh after changing language using binded strings?
I did not put a lot of code because I used basically the one provided in the link, but if you need more info I will edit my question.
I finally found a solution to automatically update my application components reacting to language change.
A good tutorial can be found here; briefly you must find a way to notify your app that localized resource is changed.
public class LocalizedStrings : ViewModelBase
{
private static AppResources localizedresources = new AppResources();
public AppResources LocalizedResources
{
get { return localizedresources; }
}
public void UpdateLanguage()
{
localizedresources = new AppResources();
RaisePropertyChanged(() => LocalizedResources);
}
public static LocalizedStrings LocalizedStringsResource
{
get
{
return Application.Current.Resources["LocalizedStrings"]
as LocalizedStrings;
}
}
}
With this when user change language, you should simply run
LocalizedStrings.LocalizedStringsResource.UpdateLanguage();
and the job is done.
Related
I have a UWP Application using ReactiveUI. I navigate to a page with this code:
Router.Navigate.Execute(new AccountListViewModel(this));
The navigation is done. But the ViewModel I created for the navigation is not assigned to my ViewModel in the View. IViewFor<> is implemented as follows:
public sealed partial class AccountListView : IViewFor<AccountListViewModel>
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty
.Register(nameof(ViewModel), typeof(AccountListViewModel), typeof(AccountListView), null);
public AccountListView()
{
this.InitializeComponent();
this.WhenActivated(disposables =>
{
// My Bindings
...
});
}
object IViewFor.ViewModel
{
get => ViewModel;
set => ViewModel = (AccountListViewModel) value;
}
public AccountListViewModel ViewModel {
get => (AccountListViewModel)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
Or do I get something completly wrong here?
According to ReactiveUI RoutedViewHost implementation for Windows, which is used for Universal Windows Platform and for Windows Presentation Foundation, the view model should definitely get assigned to the IViewFor.ViewModel property. You can track changes in the IScreen.Router.CurrentViewModel property to make sure it changes.
If it does, make sure you properly bind your IScreen.Router property to the Router property of the UWP-specific RoutedViewHost XAML control, and routing should finally work. In fact, I tested that behavior on UWP recently and it worked fine for me with ReactiveUI 9.13.1 and latest UWP SDK. Try following the routing tutorial to fully understand how routing works. If this still won't work for you, then uploading a minimal repro that compiles to GitHub could help us understand your issue better. Also, come join ReactiveUI Slack, we are always ready to help out.
I have seen this coding style:
public CustomTextCell()
{
}
public static readonly BindableProperty IsCheckedProperty =
BindableProperty.Create(
"IsChecked", typeof(bool), typeof(CustomTextCell),
defaultValue: false);
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
}
and this:
public class ExtViewCell : ViewCell
{
public bool NoTap { get; set; }
}
Can someone help explain the difference. Is one serving a different function from the other? In my case all I need is to pass to a custom renderer the value of NoTap. Should I code it like in the first or second example?
The second one is a POCO - a plain old C# object - that is relatively self-explanatory, but serves not much more purpose that holding data - and not that much in this case.
The first one is a bit more interesting, especially in the context of MVVM. SetValue does more than just setting the value, but will (in most cases) raise PropertyChanged event (see INotifyPropertyChanged), to notify subscribers that, well, a property has changed.
Now how does this relate to your custom renderer? You could implement the property in your view as a plain property - i.e. without notifications - and it might work (cannot tell, though, since I do not know your custom renderer) when setting IsChecked initially (and without binding). Anyway, imagine you'll update the value of IsChecked. You do so from your code and wonder, why this change is not reflected in your custom renderer. But how is your renderer supposed to know? Polling each and every property might be possible for smaller forms, but is a terrible waste of resources. (And Xamarin.Forms just does not work this way.) You'll page/view has to tell your custom renderer, that something has changed. INotifyPropertyChanged to the rescue. In your custom renderer you can subscribe to PropertyChanged event and react to IsChecked being changed, updating your native view.
In this example, a unique Person is defined by their FirstName and LastName. PageA is a form that selects a unique Person. PageB is a list of unique FirstNames, and PageC is a list of all the LastNames that exist for a given FirstName.
I'm having a hard time solving a particular UX pattern using MvxNavigationService. Here's what I'm attempting to do, (psuedocode):
PageA.SelectedItem = NavigateTo(PageB) [list of Person, grouped
by Person.FirstName];
PageB.SelectedItem = NavigateTo(PageC) [for
Person.FirstName, list of Person.LastName, ];
PageC.Close(SelectedItem);
PageB.Close(SelectedItem);
When I actually try and implement this and run it on Android, the viewmodel logic executes, but the UI doesn't show PageA.
Update: Calling PageB.Close() navigates back to PageC, since PageC was the previous page. Perhaps the problem could be solved by ensuring that PageC is removed from the stack upon closing it. How might this be accomplished?
There's many ways of doing this, using a Custom ViewPresenter on iOS or using an Activity on Android.
One way I achieved this for a small pair (2 view models) was by adding an Instance static variable to the first ViewModel that opens the second ViewModel, like this:
public class FirstViewModel
{
public static FirstViewModel Instance;
public void FirstViewModel()
{
Instance = this;
...
}
}
And then in the second ViewModel's save/close command, I just closed both ViewModels like this and it worked:
public new MvvmCross.Commands.IMvxCommand SaveClickCommand
{
get
{
return new MvvmCross.Commands.MvxAsyncCommand(
async () =>
{
await Navigator.Close(this);
await Navigator.Close(FirstViewModel.Instance);
}
);
}
}
I am using a CellTable to display results of a query and I need these results to be shown as (html) links. I would like to react to a click on these links. So far, I had implemented it like this:
// ClickHandler on CellTable
cellTable.addCellPreviewHandler(new Handler<OperationalItemMultipleSearchRowData>() {
#Override
public void onCellPreview(CellPreviewEvent<OperationalItemMultipleSearchRowData> event) {
boolean isClick = "click".equals(event.getNativeEvent().getType());
if (isClick)
AdminUtils.EVENT_BUS.fireEvent(new SimpleSearchEvent(event.getValue().getName()));
}
});
The Problem is that this reacted to a Click on the whole row instead of the link. Due to architectural restrictions, the link itself is not a real html link, but a SafeHtml link that leads nowhere. I just needed the look&feel of a Link:
Column<OperationalItemMultipleSearchRowData, SafeHtml> nameColumn = new Column<OperationalItemMultipleSearchRowData, SafeHtml>(new SafeHtmlCell()) {
#Override
public SafeHtml getValue(final OperationalItemMultipleSearchRowData object) {
return new SafeHtml() {
#Override
public String asString() {
return "" + object.getName() + "";
}
};
}
};
How can I react to a click on this link only ? (instead of the whoel row)
Is there a more elegant way to implement this ?
Cheers
As with any other use of event delegation, the basic idea is to find walk up the hierarchy starting from the target of the event up until you find the link element you're looking for, or some other element that signals the search is over and the click was targetted outside the link (e.g. you reached the cell, the row or the table).
That being said, I think you should merge your behavior inside a specific Cell implementation rather than using a CellPreviewHandler (copy/paste the ActionCell or TextButtonCell as a starting point).
As a side note, I also believe you should not use a link when you're not actually linking anywhere, or try to provide a target for the link if the behavior is the one of a link (that way, right-clicking, middle-clicking or ctrl-clicking will Just Workâ˘). If you want the look of a link (without the "feel"), then just use an ActionCell or TextButtonCell and style it accordingly.
Im working on a MVVM Windows phone app that displays weather info.
When the app loads up it opens MainPage.xaml. It makes a call the the service to get weather info and binds that data to the UI. Both Fahrenheit and Celcius info are returned but only one is displayed.
On the setting page, the user can select to view the temp in either Fahrenheit or Celcius.
The user can change this setting at any time and its stored in IsolatedStorageSettings.
The issue Im having is this:
when the user navigates to the Settings page and changes their preference for either Fahrenheit or Celcius, this change is not reflected on the main page.
This issue started me thinking about this in a broader context. I can see this being an issue in ANY MVVM app where the display depends on some setting in IsolatedStorage. Any time any setting in the IsoStore is updated, how does the ViewModels know this? When I navigate back in the NavigationStack from the settings page back to MainPage how can I force a rebind of the page?
The data in my model hasnt changed, only the data that I want to display has changed.
Am I missing something simple here?
Thanks in advance.
Alex
Probably you have code like this:
public double DisplayTemperature
{
get { return (IsCelsium) ? Celsium : Fahrenheit; }
}
And IsCelsium is:
public double IsCelsium
{
get { return (bool)settings["IsCelsium"]; }
set { settings["IsCelsium"] = value; }
}
So you need to add NotifyPropertyChanged event to notify UI to get new values from DisplayTemperature property:
public double IsCelsium
{
get { return (bool)settings["IsCelsium"]; }
set
{
settings["IsCelsium"] = value;
NotifyPropertyChanged("DisplayTemperature");
}
}
Take a look at Caliburn Micro. You could implement something similar or use CM itself. When using CM I don't even think about this stuff, CM makes it so simple.
When your ViewModel inherits from Screen there are life-cycle events that fire that you can override. For example, OnInitialize fires the very first time the ViewModel is Activated and OnActivate fires every time the VM is activated. There's also OnViewAttached and OnViewLoaded.
These methods are the perfect place to put logic to populate or re-populate data.
CM also has some special built in features for allowing one to easily tombstone a single property or an entire object graph into Iso or phone state.
ok, so Ive come up with a solution. Before I get to it, let me provide some background. The app that Im working on uses both MVVM Light and WP7Contrib. That being the case, I am using Funq for DI and the MVVMLight Toolkit. After I posted my initial question, I gave the question a bit more thought. I remembered a video that I watched a while back from MIX2011 called Deep Dive MVVM with Laurent Bugnion
http://channel9.msdn.com/Events/MIX/MIX11/OPN03
In it, he talks about just this problem (view models not living at the same time) on Windows Phone. The part in question starts around the 19 minute mark.
Anyway, after I remembered that and realized that the ViewModel locator is exposed in App.xaml, this became a trivial problem to solve. When the user changes the Fahrenheit/Celcius option on the setting page, I simply get a reference to the MainViewModel via the ViewModelLocator and reset the collection that is bound to the UI thus causing the bindings to update.
public bool AddOrUpdateValue(string Key, Object value)
{
bool valueChanged = false;
// If the key exists
if (settings.Contains(Key))
{
// If the value has changed
if (settings[Key] != value)
{
// Store the new value
settings[Key] = value;
valueChanged = true;
}
}
// Otherwise create the key.
else
{
settings.Add(Key, value);
valueChanged = true;
}
return valueChanged;
}
public bool ImperialSetting
{
get
{
return GetValueOrDefault<bool>(ImperialSettingKeyName, ImperialSettingDefault);
}
set
{
if (AddOrUpdateValue(ImperialSettingKeyName, value))
{
Save();
RaisePropertyChanged("ImperialSettingText");
var vml = new ViewModelLocator();
vml.MainViewModel.Cities = (App.Current as App).Cities;
}
}
}
It was a mistake on my part not to realize that I could get access to the viewModel via the ViewModelLocator. Hopefully this post saves someone else the time I burned on this issue.