I have the following in xaml.
<TextBlock Text="{Binding Title}" />
And created the following dependency property,
public string Title
{
get { return (string)GetValue(TitleProperty); }
set
{
SetValue(TitleProperty, value);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Title"));
}
}
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(ColumnChart), new PropertyMetadata(string.Empty));
Now if I bind the Title property in other xaml, the value is not taken. Because the PropertyChange notification is not called. And always PropertyChanged is null.
How I can notify to the list of observers that this property is changed, so that the value will be updated.
I'm not quite sure what you mean by "How I can notify to the list of observers that this property is changed, so that the value will be updated."
This looks like a user control, as it's not typical to use dependency properties in view models. Therefore, have a look at Routed Events. The Register() method for a dependency property has an override which will take a handler which will be invoked when the property changes. You can invoke a custom routed event from within this handler. Consumers of your user control can subscribe to this routed event using the standard mechanisms.
Related
In Xamarin for mac, I decided to make multiple views to be used within my main view using the MVVM pattern.
The thing is that I have a ListView within my MainPage which pulls a List of items from a model, and the list is populated within a child view, with its own ViewModel.
When I add a new service from the child view, I would like for the OnPropertyChanged event on the parent view model to trigger.
It is working by navigating to the parent view and setting the animation to false, but this is not really nice looking. It worked though when I had all code within one ViewModel.
How I tried to achieve this, and the errors I got:
0 - Accessing the command within the child model from the parent model, and passing the propertychanged event handler along.
I Couldn't do it. I tried this by making a bindable command like below, but this is not doable for me as I don't think it is possible for the command to know when the property will be changed, which is the whole point of this problem.
If it is doable, I don't know how.
//public static readonly BindableProperty SaveServiceClickedCommandProperty =
// BindableProperty.Create(
// "SaveServiceClicked",
// typeof(Command),
// typeof(NewServiceViewModel),
// null);
1 - Passing the parent view model on the child view model, and put a OnPropertyChanged(nameof(parentModel.List)) at the clicked event handler.
public class ChildViewModel : INotifyPropertyChanged
{
public ICommand AddEntryClickedCommand { get; private set; }
private MainModel mainModel;
// property changed handler
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public NewServiceViewModel()
{
Navigation = MainPage;
//async void execute() => await OpenPage();
//OpenPageCommand = new Command(execute, () => !IsBusy);
//async Task OpenPage()
//{
// await Navigation.PushAsync(new MainPage());
//}
// Here I tried to access the data from within the main model.
mainModel = new MainModel(Navigation);
InitMainModel();
void InitMainModel()
{
MainPage mainView = new MainPage();
mainView.BindingContext = mainModel;
}
async void c1() => await AddEntryClicked();
AddEntryClickedCommand = new Command(c1);
}
public async Task<bool> AddEntryClicked()
{
OnPropertyChanged(nameof(mainModel.List))
}
The attempt above created some errors as the object is already populated.
Leading to me thinking that I don't have the right approach altogether.
My solution being to re-introduce the child view within the parent view, and change IsVisible according to the button being clicked or not, as I already did with other smaller component.
I have thought about pulling the list from the child view, but that's raises the same issue of non-null collection.
Of course, the code has been modified to show only the gist.
Thanks in advance.
It seems like the ViewModel gets an update of the Focused state of an Entry (based on user interaction) but setting a value on the bound property in the ViewModel has no effect.
I have this in my View:
<Entry IsFocused="{Binding Focused, Mode=TwoWay}"/>
And this in my ViewModel:
public bool Focused
{
get
{
return _focused;
}
set
{
_focused = value; // This gets invoked on user interaction with UI, but setting it programatically has no effect on the Entry's focus.
OnPropertyChanged(nameof(Focused));
}
}
Why is the above not working for me?
Is it because IsFocussed is read-only? If so, why am I even allowed to specify Mode=TwoWay?
public bool IsFocused
{
get { return (bool)GetValue(IsFocusedProperty); }
}
You guess right: the IsFocused property is indeed readonly.
They indeed could throw an exception in a setter, but design decisions are tough and must be coherent between all ui elements.
I would like to display the current user in a custom view, which most of my ContentPage's contain. I currently store the current user in the App instance as a property, after login. Attempting to just update the Label in the constructor is too early in the lifecycle.
Is there a way with Xamarin Forms to bind to this object or otherwise get the current user to update a Label in my custom view? I am using Xamarin.Forms 3.5 with the standard MVVM.
There are a multiple approaches you could take, but the short answer is that you need something sitting between the global (static) variable and the views in order to make things work smoothly. The property on your view model must be a non-static property.
However it can have a custom implementation so that the getter retrieves the value from some global location, and in your case, you may not need a setter. The only other piece you need is to tell the view model to fire a PropertyChanged event when the user information is available, then you can use standard Binding from the Label to the view model.
Assuming you have some CurrentUser static class that has members like:
public static class CurrentUser
{
public event Action OnLogin; // login code needs to fire this on login
public string Username { get; set; }
// etc.
}
Then view models would hook up to that by doing something like:
class UserViewModel : INotifyPropertyChanged
{
public UserViewModel()
{
CurrentUser.OnLogin += CurrentUser_Login;
}
private void CurrentUser_Login()
{
PropertyChanged?.Invoke(nameof(Username));
}
public string Username {
get {
return CurrentUser.Username;
}
}
// etc.
}
Then the view would use <Label Text="{Binding Username}" . . .> and then when OnLogin is fired, all views would be automatically updated with the new username.
I want to know what the advantage are using the relay command to call functions that refresh the screen. In my application I have the following relay command setup.
private RelayCommand _refreshSitesCommand;
public RelayCommand RefreshSitesCommand
{
get { return _refreshSitesCommand ?? (_refreshSitesCommand = new RelayCommand(RefreshSites)); }
}
private RelayCommand _refreshProvidersCommand;
public RelayCommand RefreshProvidersCommand
{
get { return _refreshProvidersCommand ?? (_refreshProvidersCommand = new RelayCommand(RefreshProviders)); }
}
private async void RefreshSites()
{
var sitesStats = await _dataService.GetSiteStats();
if (sitesStats != null)
{
SiteStats.Clear();
foreach (var site in sitesStats)
{
SiteStats.Add(new SiteStatsViewModel(site));
}
SelectedSite = SiteStats[0];
}
}
private async void RefreshProviders()
{
var providers = await _dataService.GetProviders();
if (providers != null)
{
Providers.Clear();
foreach (var provider in providers)
{
Providers.Add(new ProviderViewModel(provider));
}
SelectedProvider = Providers[0];
}
}
Then in my code I have the following calls to execute it.
RefreshProvidersCommand.Execute(null);
RefreshSitesCommand.Execute(null);
So why is that better than just calling the RefreshSites and RefreshProviders functions. Then I would not need the code for the RelayCommand objects. Other than exposing the functionality of the 2 private functions, what benefit does using the RelayCommand object have over just making the functions public and calling them.
MVVM is in part about avoiding code-behind in the View class.
If, for example, you want an action to be taken in response to a button click, then you can either assign a Click event handler or assign the Command property to command methods. (Commands have certain advantages over Click event handlers, but that was not the question.)
There is no other good option for handling the Click event other than defining a method in the View class. You cannot directly assign the Click event to a handler method in a different class than the View and you can bind only to properties, not methods.
However, you can assign the Command property to a binding to an object that implements the ICommand interface, e.g. a RelayCommand, and that binding can be to a property of your ViewModel object. This avoids having to define Click event handlers in the view's code behind file and at the same time gives your ViewModel the ability to easily enable/disable commands without needing to know anything about the View's specific implementation.
One can argue about the merits of religiously avoiding code-behind, but that was not the question asked.
Because you can bind to a Command in your view. You can't bind to methods in your views (well you can but binding to Commands is much cleaner)
RelayCommand also implements a CanExecute method which, when binding your RelayCommand to a button, is used to automatically toggle the button's IsEnabled property based on the action you specified for the CanExecute method.
I have 2 projects in my solution.
MVC Web application
Class library
The MVC Web application references the class library.
The class library contains a class that extends the default ASP.Net Controller.
I'm putting a variable in session in the application's Global.asax.
protected void Session_Start(object sender, EventArgs args)
{
HttpContext.Current.Session["DomainName"] = Request.Url.Host;
}
In the class library I'm trying to get the value from the HttpContext.Session, but HttpContext.Session keeps coming up null.
public class MyController : System.Web.Mvc.Controller
{
public MyController () : base()
{
//HttpContext.Session is always null at this point
ViewData["DomainName"] = HttpContext.Session["DomainName"];
}
}
HttpContext.Current.Session doesn't seem to be an option in controllers. Any ideas?
Two issues -- the HttpContext property in the Controller class is the current session. Unfortunately, it's not available in the constructor of the controller. Obviously because it's not passed in the constructor, it has to be set via the property afterwards. You might consider adding a property to hold the domain name and referencing the session from it -- that way it would be available for use when needed.
protected string DomainName
{
get { return this.HttpContext.Session["DomainName"] as string; }
}
The set it in ViewData in your actions or in OnActionExecuting/OnActionExecuted.
protected override void OnActionExecuted( ActionExecutedContext context )
{
ViewData["DomainName"] = this.HttpContext.Session["DomainName"];
// or ViewData["DomainName"] = this.DomainName; // if you used the property
}
If you're just trying to add ViewData from the session, try doing it in the OnActionExecuting method. This is where I typically add ViewData I want for every View.
You just use Session by itself (it's a property of Controller), but that just maps to Controller.HttpContext.Session (in other words, what you're already using), so it won't solve your problem, which must be elsewhere.
I'm not sure why you're putting this in the Session, though, as you can read Request.Url.Host directly during the Action.
When you create cookie then you must write
Response.AppendCookie("Your cookie name");
And if you want to get that then something like this
if (Request.Cookies["Your cookie name"] != null)
{
string value = Request.Cookies["Your cookie name"].Value;
}
and must if there are different solutions
then
machineKey
need to be same which is under
system.web
in web.config and then write
<httpCookies domain=".yourdomainname.com" />