Composite Binding to property and inner viewmodel property not firing - c#

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.

Related

WPF Binding not updating even though PropertyChanged fires

I have a view model that I bind to my view via
public MyViewModel viewModel = new MyViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = viewModel;
}
MyViewModel implements the INotifyPropertyChanged interface with the standard
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
MyViewModel has a string member called Status which is defined with
private string _status = string.Empty;
public string Status
{
get => _status;
set
{
if (_status != value)
{
_status = value;
OnPropertyChanged();
}
}
}
The Status property is used in my view like so:
<TextBlock Text="{Binding Status}"></TextBlock>
As my application runs, the value of Status periodically changes. I can see that OnPropertyChanged fires correctly in each case, however the view only changes some of the time. I have also noticed (by logging to the console when a property changes) that the property sometimes changes (and fires the change event) but then takes a few seconds before the View updates randomly.
I believe this has something to do with the binding. I can't afford to manually trigger a bindingExpression.UpdateTarget() manually, as the property changes when certain things happen under the hood of the view model (outside of the influence of the view).
I expected the view to update as soon as the binded property changed, but it did not.
I have tried changing the binding mode to TwoWay as well as binding the View Model within the XAML but nothing changes. There must be something I am missing. Appreciate any help.

Display Activity Indicator on property change in mvvmcross

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();
}
}

Binding to Template10 Settings from Main Page

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.

Notify ViewModel when View's RenderTransformProperty changes

I have a two-way binding on the UIElement RenderTransformProperty. Connecting from the ViewModel to the View works fine, and the ViewModel can read an updated value from the View, but the ViewModel is not aware when the property in the View changes. I know I could create my own DependencyProperty and shuttle the value but it seems wrong. How do I properly notify the ViewModel when the View's RenderTransformProperty changes.
In the View:
var renderTransformBinding = new Binding
{
Path = new PropertyPath("SymbolTransform"),
Mode = BindingMode.TwoWay
};
SetBinding(RenderTransformProperty, renderTransformBinding);
In the ViewModel (using MVVM Light):
private Transform _symbolTransform;
public Transform SymbolTransform
{
get { return _symbolTransform; }
set
{
if (Set(() => SymbolTransform, ref _symbolTransform, value))
{
_isDirty = true;
}
}
}
I was mistaken. The ViewModel is being notified of the change - i.e. the setter is being called. For some reason _symbolTransform and value are equal so if (Set(() => SymbolTransform, ref _symbolTransform, value)) is returning false. No idea how _symbolTransform is (apparently) being updated before the setter is called.

View Model not updating UI when inside constructor

I have a feeling that my view isn't being updated because the NotifyPropertyChanged event is firing prior to the UI being constructed but I don't know how to overcome this.
I am not really posting code for analysis because I know that the databindings work. They just fail during the construction of the page.
I am strictly posting it so you can get an idea of what I am talking about.
public Obj1 SelectedObj1
{
get { return _SelectedObj1; }
set { _SelectedObj1 = value; NotifyPropertyChanged("SelectedObj1"); }
}
public Obj2 SelectedObj2
{
get { return _SelectedObj2; }
set { _SelectedObj2= value; NotifyPropertyChanged("SelectedObj2"); }
}
public Obj3 SelectedObj3
{
get { return _SelectedObj3; }
set { _SelectedObj3 = value; NotifyPropertyChanged("SelectedObj3"); }
}
Inside my constructor
public constructor(){
BuildFakeData();
SelectedObj1 = observableCollection[0];
SelectedObj2 = SelectedObj1.obj2s.Count > 0 ? SelectedObj1.obj2s[0] : null;
SelectedObj3 = SelectedObj2.obj3s.Count > 0 ? SelectedObj2.obj3s[0] : null;
}
My question is, when you are doing MVVM, if you set bound properties in the constructor, say for a DataGrid selected Row, will it populate or is it failing because the XAML isn't built yet?
Here is where the datacontext is created in the view
<Window.Resources>
<vm:ViewModel x:Key="viewModel"/>
</Window.Resources>
<Grid
DataContext="{StaticResource viewModel}">
Here is where I am setting the selected item for the grid
<igWPF:XamDataGrid
ActiveDataItem="{Binding SelectedObj1}"
DataSource="{Binding observableCollection}"
If your view doesn’t exist yet when the view model is created, then of course, the view isn’t listening yet when your properties update. However, when the view is then created and the view model is assigned as its data context, then the view will automatically load the values from the view model (its data context).
So, INPC shoulnd’t be an issue there at all. You could create properties without INPC in your example and have it work (since the values are already set in the constructor).

Categories