I am trying to show loading screen when a network request is made. I have a LoginViewModel where I have defined a property IsLoading. Now when it changes I want to display activity indicator. I am trying to use BTProgressHUD here. Although I have been able to bind my controls with the ViewModel but I am not able to bind BTProgressHUD as show or hide. Following is how I am binding other controls:
var set = this.CreateBindingSet<LoginView, LoginViewModel>();
set.Bind(txtUser).To(vm => vm.Username);
set.Bind(txtPassword).To(vm => vm.Password);
set.Bind(btnLogin).To(vm => vm.LoginCommand);
set.Apply();
i have tried following for BTProgressHUD but it doesn't work:
set.Bind(BTProgressHUD.Show()).To(vm => vm.IsLoading);
I am not able to compile as I am getting error for Bind function. Is it possible to create a closure(like in iOS) bound with a property in ViewModel and for any change in property can do some operation in View?
I presume your LoginCommand calls a login function. In that function you should call BTProgressHUD.Show(), followed by your login process.
You should wrap your login process in a try/catch and in the finally you should call BTProgressHUD.Dismiss().
This has the added benefit of not having to repeat the code in all of your platforms.
As an extra, you could create a BaseViewModel which your view models inherit from, give it an IsLoading property, and set your BTProgressHUD.Show() and BTProgressHUD.Dismiss() whenever it is called.
private bool _isLoading = false;
public virtual bool IsLoading
{
get { return _isLoading; }
set
{
SetProperty(ref _isLoading, value);
if (value)
BTProgressHUD.Show();
else BTProgressHUD.Dismiss();
}
}
Related
I am loving Template10 so far, very nice.
I am a little stuck though on how to bind to a Setting value on the Main Page.
I have added a new bool setting which is storing properly.
On my main page I have a Visibility binding to the setting:
Visibility="{Binding UseAmbientLightSensor, Converter={StaticResource CollapsedWhenFalseConverter}}"
This works on app start as expected, the MainPageViewModel reads the value from Settings and a grid is visible or collapsed based on that setting.
However I cannot seem to get this binding to 'listen' to the setting, if I go to the settings page and change that value, when I go back to the Main Page the visibility does not change. It only works if I restart the app.
In the vanilla Template10 install this would be akin to Binding a little logo on MainPage to the 'UseLightThemeButton' setting in the Settings page which changes based on that setting..
Okay, so I guess this is the "official" answer. But many approaches are valid. This one matches most closely to the templates. I would do it like this:
public class MainPageViewModel : ViewModelBase
{
Services.SettingService.SettingService _SettingService;
public MainPageViewModel()
{
_SettingService = Services.SettingService.SettingService.Instance;
}
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
Windows.Storage.ApplicationData.Current.DataChanged += SettingsChanged;
await Task.CompletedTask;
}
public override async Task OnNavigatedFromAsync(IDictionary<string, object> pageState, bool suspending)
{
Windows.Storage.ApplicationData.Current.DataChanged -= SettingsChanged;
await Task.CompletedTask;
}
private void SettingsChanged(Windows.Storage.ApplicationData sender, object args)
{
RaisePropertyChanged(nameof(FontSize));
}
public double FontSize { get { return _SettingService.FontSize; } }
}
With that view-model, you can easily bind to a setting (in this case FontSize).
Best of luck.
There are two possible scenarios that may not be happening:
Raise the property change event when your bool value gets updated.
Set the binding to a two way mode.
In order to do this change the binding mode of your Visibility property
Visibility="{Binding UseAmbientLightSensor, Mode=TwoWay, Converter={StaticResource CollapsedWhenFalseConverter}}"
This will tell xaml to listen to any change on the property in the view model.
Then you need to tell the View model when to let the XAML view know of its changes, if you are using Template10, then it can be done as follows:
private bool useAmbientLightSensor;
public TodoListControlViewModel UseAmbientLightSensor
{
get
{
return this.useAmbientLightSensor;
}
set
{
this.Set(ref this.useAmbientLightSensor, value);
}
}
The view model needs to extend from the ViewModelBase class which provides the Set method that raises the OnPropertyChanged event, allowing the view to know of any change in the view model.
For more info, check the INotifyPropertyChanged interface and its implementation.
I have a custom implemented control which is very memory intensive (it basically displays a group header and an accordionControl for each group).
The CustomControls AccordionControlGrouping and AccordionControl both inherit from StackLayout, so I have implemented my own bindable ItemsSource. I actively change the views of the controls when the ItemsSource changes.
I have optimized this change now (using LINQ and BinarySearch and that stuff) to get from 5sec. per search to 0.8sec.
This wouldn't be a problem per se, but each time a user enters a key the keyboard freezes and is therefore very unconvinient to search.
My SearchBar is bound to the command of a ViewModel like this
<SearchBar x:Name="EventSearchBar"
Text="{Binding SearchText}"
SearchCommand="{Binding SearchCommand}"
Placeholder="Suche"/>
SearchCommand
private Xamarin.Forms.Command _searchCommand;
public System.Windows.Input.ICommand SearchCommand
{
get
{
_searchCommand = _searchCommand ?? new Xamarin.Forms.Command(UpdateAccordionControlGrouping, CanExecuteSearchCommand);
return _searchCommand;
}
}
private bool CanExecuteSearchCommand()
{
return true;
}
SearchText Property
private string _searchText = string.Empty;
public string SearchText
{
get { return _searchText; }
set
{
if (_searchText != value)
{
_searchText = value ?? string.Empty;
RaisePropertyChanged(nameof(SearchText));
// Perform the search
if (SearchCommand.CanExecute(null))
SearchCommand.Execute(null);
}
}
}
UpdateAccordionControlGrouping
private void UpdateAccordionControlGrouping()
{
// Do some LINQ stuff to group and filter a global collection of data.
// And raise the #OnPropertyChanged event for the ItemsSource of my
// custom control.
}
Now how do I make this whole process async?
I have already looked into this article and it didn't work for me.
To run your method 'UpdateAccordionControlGrouping()' async you can do this:
private async Task UpdateAccordionControlGrouping()
{
var newgrouping = await Task.Run(() =>
{
//do CPU bound work here
return newgrouping;
});
// assign newgrouping to the correct control
}
Be aware that if you need to change any data from the view (Bindings etc) you might get a CrossThreadException. You need to use a Dispatcher then as seen here: Dispatcher.Dispatch on the UI thread
If you work with MvvmLight you get to use the DispatchHelper Simple example of DispatcherHelper
I have created an application which uses WPF and MVVM following this article from CodeProject.
I have a view, TVSeriesView, which has a TVSeriesViewModel. These two are connected using a DataTemplate which is done following the article.
<DataTemplate DataType="{x:Type Implementation:TVSeriesViewModel}">
<TVSeriesLibrary:TVSeriesView />
</DataTemplate>
The idea is to pass my model, the TVSeries, to this ViewModel as I have a property named TVSeries in the ViewModel. When this property is set, I will populate other properties such as Title, Cover and so on. These properties are meant to be binded to controls in the view.
public class TVSeriesViewModel : ViewModelBase, ITVSeriesViewModel
{
private TVSeries _tvSeries;
private string _title;
private ImageSource _cover;
public TVSeries TVSeries
{
get
{
return this._tvSeries;
}
set
{
this._tvSeries = value;
}
}
public string Title
{
get
{
return this._title;
}
set
{
this._title = value;
OnPropertyChanged("Title");
}
}
public ImageSource Cover
{
get
{
return this._cover;
}
set
{
this._cover = value;
OnPropertyChanged("Cover");
}
}
}
First and foremost, does this sound like the right way to do it?
Next, does anyone know how to pass a parameter (a TVSeries object) to the ViewModel when the TVSeriesView is shown?
And lastly, does anyone know how I can directly access resources in the view? For example if I don't want to use data binding but instead want to set the image directly like this:
myImage.ImageSource = myImageSource
The View and ViewModel together are one of the possible representations of the Model.
You can pass a repository handle which would be eventually responsible for data access or
Concrete/abstract object of Model through Dependency Injection via Constructor or
Dependency Injection, via property/method or
In more crude way you can write a DB access code in your VM (obviously it's not suggested.)
I would prefer as the order given here. Your code is doing the third option.
I've created a property "IsLoading" for my main view model. The idea is that a progressbar is displayed whenever this property is set to true. So far so good
The catch is, that I have a command, that calls another viewmodel (the code is there because it's a functionality from another page, but I want to be able to shortcut it as well from my main viewmodel)
So, I went ahead and modified the main property to something like this :
public const string IsLoadingPropertyName = "IsLoading";
private bool _isLoading;
public bool IsLoading
{
get
{
return _isLoading || ((ViewModelLocator)Application.Current.Resources["Locator"]).SettingsViewModel.IsLoading;
}
set
{
if (value != _isLoading)
{
_isLoading = value;
RaisePropertyChanged(IsLoadingPropertyName);
}
}
}
and the xaml
<shell:SystemTray.ProgressIndicator>
<shell:ProgressIndicator IsIndeterminate="true" IsVisible="{Binding Main.IsLoading, Source={StaticResource Locator}}" />
</shell:SystemTray.ProgressIndicator>
So, I'm saying that main view model is loading when there's something loading there, or if the settings view model is loading.
The problem is that the binding only works when setting the main view model's IsLoading property, it doesn't react when I set it in the inner IsLoading one. Both have the same property name "IsLoading". Shouldn't it be detected?
For example, in Main view model (just the execution of the command for simplicity) :
private void ExecuteRefreshCommand()
{
ViewModelLocator viewModelLocator = Application.Current.Resources["Locator"] as ViewModelLocator;
viewModelLocator.SettingsViewModel.GetCurrentLocationCommand.Execute(null);
}
and inside the settings view model :
public RelayCommand GetCurrentLocationCommand
{
get
{
Action getLocation = () =>
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
return;
}
var watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.Default);
watcher.PositionChanged += WatcherPositionChanged;
IsLoading = true; // settings view model "IsLoading" propertychanged raising property
watcher.Start();
};
return new RelayCommand(getLocation);
}
}
You're looking at the MainViewModel's isLoading property to determine whether to show the progressbar or not. Silverlight uses the NotifyPropertyChanged event to determine when it should reevaluate a certain property. When setting either the SettingsViewModel's IsLoading property- or the MainViewModel's property, you only raise the changedEvent for that ViewModel. You should raise the ChangedEvent for both.
A modified setter example could be (depending on the exposed methods)
set
{
if (value != _isLoading)
{
_isLoading = value;
RaisePropertyChanged(IsLoadingPropertyName);
((ViewModelLocator)Application.Current.Resources["Locator"]).SettingsViewModel.RaisePropertyChanged(IsLoadingPropertyName);
}
}
Note that many MVVM frameworks offer a functionality called Messaging which is ideal to do cross ViewModel communication without creating the strict dependency you created right now. Alternatively you can use a globally consumed IsLoading property.
Problem:
A control that shows each user which quizzes they have passed out of a possible four.
My solution:
Create user control that lists the name of the quizzes and has a checkmark at the end of each quiz name that I would like to make visible when they pass a quiz.
The actual user control is inside of my master page.
From reading other posts, I understand that I need to make the image.visible property public in the control code behind. I have tried this several ways and haven't had much luck.
So how do I expose the .visible property of an image inside of my user control?
Thanks for any advice.
Try something like this:
public Boolean ImageIsVisible
{
set { this.yourImage.Visible = value; }
get { return this.yourImage.Visible; }
}
hree ways I can think of...
In the USER control, just set the image as public instead of default - private, but that exposes ALL the elements.
Another is to create a property at the user control level that passes on to the ex:
public Boolean ImgVisible
{
get { return this.YourImageControl.Visible; }
set { this.YourImageControl.Visible = value; }
}
Or, just create as a function in your user control...
public void ImgVisible( Boolean ShowIt )
{
this.YourImageControl.Visible = ShowIt;
}
Sorry, missed part about Master Page... As a web control, as long as control is visible from the IDE (visual designer) of your form, you can refer to it directly in the CONTROL's code-behind partial class definition by the explicit name reference...
public Boolean ImgVisible
{
get { return ImgControl.Visible; }
set { ImgControl.Visible = value; }
}
I'm having a problem accessing this on the content page. Is this right?
I went to codebehind on control and added the following property:
public Boolean ImgVisible
{
get { return this.imgModule1Passed.Visible; }
set { this. imgModule1Passed.Visible = value; }
}
on the content page code behind, I am using the following:
UserControl control = (usercontrol)this.Page.Master.FindControl("QuizzesPassed1");
Shouldn't I be able to find the property by:
Control.ImgVisible
?
I can't seem to find the control at all.