I have 2 pages, when I navigate from the first one to the other, I send it an id (in NavigationEvent). Then I call a function on its ViewModel with the id passed and which loads an object to the ViewModel's property asynchronously by a service. I binded the properties of the object in my view and try to call PropertyChanged.Invoke in the getters of the object, but it's always null. How can I bind my view to this obejct?
The data class I want to bind:
class Dog: INotifyPropertyChanged
{
private int _name;
public int Name
{
get => _name;
set
{
_name= value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
The ViewModel:
class MyViewModel : ViewModelBase
{
public Dog Dog{ get; set; }
public MyViewModel()
{
Dog = new Dog();
}
public async void LoadDog(string id)
{
var service = newvDogService();
Dog = await service.GetDogAsync(id);
}
}
The view:
public sealed partial class DogPage : Page
{
private string dogId { get; set; }
public DogPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
dogId = e.Parameter.ToString();
ViewModel.LoadDog(dogId);
}
}
In the xaml file:
<Page.DataContext>
<ViewModels:MyViewModel x:Name ="ViewModel"/>
</Page.DataContext>
...
Text="{Binding Dog.Name}"
I suggest you could simplify the binding process. For an object binding, you don’t need to use the ViewModel. When you pass the id in NavigationEvent, you could get the new Dog object directly via the id. At first, you could create a Dog object that Name property is null. Then you could change the previous object Name through the newly created object.
Please refer to the following code.
Xaml code:
<StackPanel DataContext="{x:Bind Dog,Mode=OneWay}">
<TextBlock Text="{Binding Name,Mode=OneWay}"/>
</StackPanel>
Code behind:
public sealed partial class MainPage : Page
{
public Dog Dog { get; set; }
public MainPage()
{
this.InitializeComponent();
Dog = new Dog { Name = "" };
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
dogId = e.Parameter.ToString();
var dog = await service.GetDogAsync(dogId);
Dog.Name=dog.Name;
}
}
public class Dog : INotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Related
I have two buttons and bind their property to two properties of a data object.
But every property is updated when I call PropertyChanged of the data object.
public partial class Form1 : Form
{
private DataClass data = new DataClass();
public Form1()
{
InitializeComponent();
ButtonA.DataBindings.Add("Text", data, "DataA");
ButtonB.DataBindings.Add("Text", data, "DataB");
ButtonB.Click += new EventHandler(OnButtonBClicked);
}
private void OnButtonBClicked(object sender, EventArgs e)
{
data.DataA += "1";
data.DataB += "1";
data.Notify("DataB");
}
}
public class DataClass : INotifyPropertyChanged
{
public string DataA { get; set; }
public string DataB { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public DataClass() {}
public void Notify(string property_name)
{
PropertyChanged(this, new PropertyChangedEventArgs(property_name));
}
}
When I press ButtonB (which means I call PropertyChanged(this, new PropertyChangedEventArgs("DataB"))), both ButtonA and ButtonB show new text.
If I call PropertyChanged(this, new PropertyChangedEventArgs("DataA")), both buttons are updated.
If I don't change value of DataA / DataB and just call PropertyChanged(this, new PropertyChangedEventArgs("DataB")), still both buttons are updated (can be noticed by breakpoint debugging).
If I call PropertyChanged(this, new PropertyChangedEventArgs("QQQ")), then no button is updated.
PropertyChangedEventArgs has a property named propertyName, I thought it's used to specify one property to notify but it doesn't.
In my real code, DataB changes much more frequently than DataA. I don't want to update ButtonA each time DataB is changed, it takes too much time.
Question: why would this happen? When a data source property is changed, how can I only update properties really connected to it?
(All code is .Net Framework 4.7.1 on Windows.)
#Jimi's method works.Simple and effective.I put each property in a shell class and bind data to the shell:
public class MyProperty<T>: INotifyPropertyChanged
{
public T Content { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public MyProperty(T _content)
{
Content = _content;
}
public void Notify()
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public class DataClass
{
public MyProperty<string> DataA = new MyProperty<string>("");
public MyProperty<string> DataB = new MyProperty<string>("");
public DataClass() {}
}
But in this way I must use DataA.Content+="1" instead of DataA+="1" every where.
I decide to use a base class to create all shells.But my real DataClass must inherit from other class and C# don't support multi-inherit.So I have to use a extension class.
public class BindHandle<T> : INotifyPropertyChanged
{
public T Content { get { return (T)parent.GetType().GetProperty(prop_name).GetValue(parent); } }
private object parent;
private string prop_name;
public event PropertyChangedEventHandler PropertyChanged;
public BindHandle(object _parent, string _prop_name)
{
parent = _parent;
prop_name = _prop_name;
}
public void NotifyChange()
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public interface IBindHandleProvider
{
BindHandleProvider provider { get; set; }
}
public class BindHandleProvider
{
private Dictionary<string, object> handle_map = new Dictionary<string, object>();
public BindHandle<T> GetBindHandle<T>(object obj,string property_name)
{
if (!handle_map.ContainsKey(property_name))
handle_map.Add(property_name, new BindHandle<T>(obj, property_name));
return (BindHandle<T>)handle_map[property_name];
}
public void NotifyChange<T>(string property_name)
{
if (handle_map.ContainsKey(property_name))
((BindHandle<T>)handle_map[property_name]).NotifyChange();
}
}
public static class BindHandleProviderExtension
{
public static void NotifyChange<T>(this IBindHandleProvider obj, string property_name)
{
obj.provider.NotifyChange<T>(property_name);
}
public static BindHandle<T> GetBindHandle<T>(this IBindHandleProvider obj, string property_name)
{
return obj.provider.GetBindHandle<T>(obj,property_name);
}
}
public class DataClass:IBindHandleProvider
{
public BindHandleProvider provider { get; set; } = new BindHandleProvider();
public string DataA { get; set; } = "";
public string DataB { get; set; } = "";
public DataClass(){ }
}
Then bind it like
ButtonA.DataBindings.Add("Text", data.GetBindHandle<string>("DataA"), "Content");
And notify like
data.NotifyChange<string>("DataB");
It's kinda complex but works well.
Is it possible to notify changes on a child class? Like the way binding on ValueB is notified when changing ValueA?
The PropertyChangedEventHandler only allows a propertyname to be notified.
The only way I see is adding functionality to the Child class to call notification there (Notify method)..
public class Parent: INotifyPropertyChanged
{
public Child ChildA
{
get; set;
}
public Child ChildB
{
get; set;
}
public int ValueA
{
get
{
return _valueA;
}
set
{
_valueA = value;
OnPropertyChanged(nameof(ValueA));
}
}
public int ValueB
{
get
{
return _valueB;
}
set
{
_valueB = value;
OnPropertyChanged(nameof(ValueA));
OnPropertyChanged(nameof(ValueB));
}
}
public void RefreshBindings()
{
OnPropertyChanged(ChildA.Check);
OnPropertyChanged(ChildB.Check);
}
}
public class Child: INotifyPropertyChanged
{
public void Notify(string property)
{
OnPropertyChanged(property);
}
public bool Check
{
get
{
return // something;
}
}
}
No, it's the source of the binding that should implement the INotifyPropertyChanged interface and raise change notifications for the framework to be able to refresh the bindings "automatically".
So if you bind to ChildA.Check of Parent, it's the object returned by the ChildA property (i.e. the Child class) that should implement INotifyPropertyChanged.
The other option would to bind to properties of Parent that wraps properties of Child, but the Child must still somehow notify the parent when its state changes.
#NawedNabiZada I appreciate you suggestions but they do not work.
Please only suggest it if you know for a fact they work.
Not sure what you tried, but my point is this:
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Child A :"/>
<Label Content="{Binding Path=ChildA.Check}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Label Content="Child B :"/>
<Label Content="{Binding Path=ChildB.Check}"/>
</StackPanel>
<Button Content="Check/UnCheck" Command="{Binding Path=RefreshBindingCommand}"/>
</StackPanel>
</Grid>
Parent:
public class Parent : INotifyPropertyChanged
{
public Child ChildA
{
get; set;
}
public Child ChildB
{
get; set;
}
public ICommand RefreshBindingCommand { get; }
public Parent()
{
ChildA = new Child(true);
ChildB = new Child(false);
RefreshBindingCommand = new RelayCommand(RefreshBindingCommand_Execute);
}
void RefreshBindingCommand_Execute(object obj)
{
RefreshBindings();
}
public void RefreshBindings()
{
ChildA.Notify(nameof(ChildA.Check));
ChildB.Notify(nameof(ChildB.Check));
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Child:
public class Child : INotifyPropertyChanged
{
private bool _check;
public bool Check
{
get
{
_check = !_check;
return _check;
}
}
public Child(bool check)
{
_check = check;
}
public void Notify(string property)
{
OnPropertyChanged(property);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Proof that it works:
I got two pages, "HomePage", "SettingPage", including the same "MyView" (some Pickers there).
When I click "Go Setting"(or show more settings) Button from Homepage, the values syncs to the setting page. But When I click "Apply" on the setting page, the values did not come back.
I am new in c# and Xamarin and tried to search online and Microsoft docs. But I couldn't find a way to fix this issue.
Also I was following this link: How to set BindingContext of multiple pages to the same ViewModel in Xamarin.Forms?
and did the same global value in my code.
MyView (ContentView)
public MyView()
{
InitializeComponent();
BindingContext = GlobalVar.MyViewModel;
Setting1.SetBinding(Picker.ItemsSourceProperty, "ObList1");
Setting1.ItemDisplayBinding = new Binding("obj_text");
Setting1.SetBinding(Picker.SelectedItemProperty, "SelectedItem1");
//also other pickers
}
HomePage (including the MyView)
public SearchPage ()
{
InitializeComponent ();
BindingContext = GlobalVar.MyViewModel;
}
private async void Click_GoSetting(object sender, EventArgs e)
{
await Navigation.PushAsync(new SettingPage());
}
SettingPage (including the same MyView)
public partial class SettingPage : ContentPage
{
MyViewModel viewModel { get; set; } = GlobalVar.MyViewModel;
public SettingPage ()
{
BindingContext = viewModel;
}
private async void Click_ApplySetting(object sender, EventArgs e)
{
await Navigation.PopAsync(true);
}
//some other method deal with viewModel
}
GLobalVar.cs
private static MyViewModel _myViewModel = new MyrViewModel();
public static MyViewModel MyViewModel
{
get
{
return _myViewModel;
}
}
ViewModel
public class MyViewModel : BaseViewModel
{
public ObservableCollection<obj> ObList1 { get; set; }
public ObservableCollection<obj> ObList2 { get; set; }
public ObservableCollection<obj> ObList3 { get; set; }
public obj SelectedItem1 { get; set; }
public obj SelectedItem2 { get; set; }
public obj SelectedItem3 { get; set; }
public MyViewModel()
{
ObList1 = new ObservableCollection<obj>();
ObList2 = new ObservableCollection<obj>();
ObList3 = new ObservableCollection<obj>();
}
}
Maybe I should notify the changes on my SettingPage to viewmodel? or do something in the "set" in viewmodel?
The confusing point is that two pages embed the same view using the same viewmodel, but notify the change from Page1 to Page2 only, not Page2 to Page1.
Any ideas, thx in advance.
Solution One:
Using Event can pass value back to Previous Page.
Define Event in SecondPage :
public delegate void EventHandler(string status);
public event EventHandler EventPass;
Invoke Event when Page disappear:
protected override void OnDisappearing()
{
base.OnDisappearing();
EventPass("Back Code");
}
In FirstPage, when Naviagtion place need to add the Event here:
string title = "PageSecondParamater";
PageSecond pageSecond = new PageSecond(title);
pageSecond.EventPass += PageSecond_EventPass; ;
Navigation.PushAsync(pageSecond);
Now value will be passed here:
private void PageSecond_EventPass(string status)
{
Title = status;
Console.WriteLine("---" + status);
}
Solution Two:
Using Properties Dictionary to store easy and small size data in Application, when enter in page will invoke it to get data from which has been stored.
In Second Page Where you want to store data, writing as bellow:
Application.Current.Properties ["value"] = valuedata;
When back to First Page, override OnAppearing method to update UI:
protected override void OnAppearing()
{
base.OnAppearing();
if (Application.Current.Properties.ContainsKey("value"))
{
var ValueGet = Application.Current.Properties ["value"] as DataType;
// do something with other things
}
}
Note: ViewModel if want to dynamic update data , need to use INotifyPropertyChanged .
Sample Implementation:
public class ObservableProperty : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModelBase suggest implementing ICommand as a Dictionary structure like:
public abstract class ViewModelBase : ObservableProperty
{
public Dictionary<string,ICommand> Commands { get; protected set; }
public ViewModelBase()
{
Commands = new Dictionary<string,ICommand>();
}
}
So all todo in your ViewModel is just inherit the ViewModelBase class and use it:
class LoginViewModel : ViewModelBase
{
string userName;
string password;
public string UserName
{
get {return userName;}
set
{
userName = value;
OnPropertyChanged("UserName");
}
}
public string Password
{
get{return password;}
set
{
password = value;
OnPropertyChanged("Password");
}
}
#endregion
#region ctor
public LoginViewModel()
{
//Add Commands
Commands.Add("Login", new Command(CmdLogin));
}
#endregion
#region UI methods
private void CmdLogin()
{
// do your login jobs here
}
#endregion
}
Solved.
MyViewModel (updated)
public class MyViewModel : BaseViewModel
{
public ObservableCollection<obj> ObList1 { get; set; }
public ObservableCollection<obj> ObList2 { get; set; }
public ObservableCollection<obj> ObList3 { get; set; }
private obj _selectedItem1 = new obj();
public obj SelectedItem1
{
get { return _selectedItem1; }
//this is the line solved the problem
//but still not understood thoroughly
set { SetProperty(ref _selectedItem1, value); }
}
//same for _selectedItem2 _selectedItem3
}
ps: BaseViewModel codes here (not changed, from template codes)
public class BaseViewModel : INotifyPropertyChanged
{
//some other attributes
//...
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
It seems that by calling SetProperty, OnPropertyChanged will also be revoked.
But still a little bit confusing about why the previous codes go like kind of "one-way" binding.
I'm having trouble with a simple image source binding.
I have a class that store the path to the image file (and other stuff) which look like this:
public class Ekta {
...
public string PATHMED { get; set; }
public string FICMED { get; set; }
public string FULLPATH { get { return PATHMED + FICMED; } }
...
}
I have the following property in my window:
public Ekta mainImg { get; set; }
And in the xaml, the binding is done like this:
<Image Source="{Binding Path=mainImg.FULLPATH}"/>
This work well when I set mainImg's value the first time (Before InitializeComponent() is called), but when I update it (mainImg = e; where e is an instance of Ekta) the UI doesn't change.
Am I missing something ? Is it the right way to bind an image source to a custom item ?
I suggest to make a base class named Notifier and use it for any class which needs INotifyPropertyChanged implementation
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then
public class Ekta : Notifier
{
private string _PATHMED;
public string PATHMED
{
get { return _PATHMED; }
set
{
_PATHMED = value;
RaisePropertyChanged();
RaisePropertyChanged("FULLPATH");
}
}
private string _FICMED;
public string FICMED
{
get { return _FICMED; }
set
{
_FICMED = value;
RaisePropertyChanged();
RaisePropertyChanged("FULLPATH");
}
}
public string FULLPATH
{
get { return PATHMED + FICMED; }
}
}
What I am trying to achieve
I have a WPF application (it's just for testing) and I want to bind the text (Content) of a label to a property somewhere. The idea is that this property value will be changed when the user chooses a different language. When the property changes, I want the label text to update with the new value.
What I have tried
I tried to create a static class with a static property for the label value. For example:
public static class Language
{
public static string Name = "Name";
}
I then was able to bind this value to my label using XAML like so:
Content="{Binding Source={x:Static lang:Language.Name}}"
And this worked fine for showing the initial value of "Name". The problem is, when the Name property changes the label value doesn't change.
So, back to the drawing board (Google). Then I found this answer which sounded exactly like what I needed. So here was my new attempt at this:
public class Language
{
public static Language Instance { get; private set; }
static Language() { Instance = new Language(); }
private Language() { }
private string name = "Name";
public string Name { get { return name; } set { name = value; } }
}
With my binding changed it this:
Content="{Binding Source={x:Static lang:Language.Instance}, Path=Name}"
This still results in the same problem.
Questions
What am I missing here? How can I get the label to update when the value is changed?
That simply isn't a property. Try:
public class Language
{
public static Language Instance { get; private set; }
static Language() { Instance = new Language(); }
private Language() { Name = "Name"; }
public string Name {get;private set;}
}
or with change notification:
public class Language : INotifyPropertyChanged
{
public static Language Instance { get; private set; }
static Language() { Instance = new Language(); }
private Language() { }
private string name = "Name";
public string Name
{
get { return name; }
set { SetValue(ref name, value);}
}
protected void SetValue<T>(ref T field, T value,
[CallerMemberName]string propertyName=null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(propertyName);
}
}
protected virtual void OnPropertyChanged(
[CallerMemberName]string propertyName=null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}