WPF Binding between pages / Transfer data between view models - c#

I need to transfer data between pages and do "binding" on it.
On the first page i have textbox bound to "Username" property.
Every page has its own viewmodel, after clicking a button in the first page i've done something like this
SecondPageViewModel.Username = this.Username;
In second page i have textblock bound to Username property, but after page changes, the second page show no text.
<TextBlock Text="{Binding Username}" />
The only way i found and works is to in the second page viewmodel in the constructor make a task which updates the username.
Task.Run(async () =>
{
while(true)
{
await Task.Delay(200);
this.Username = FirstPageViewModel.Username;
}
});
Is there any other way to do that? By making task here, it isn't always working, sometimes if i change page too fast, it won't show username anyway.
Every viewmodel implements INotifyPropertyChanged + FodyWeaver.

Following my comment, here's some simple implementations using events.
A first implementation is FirstPageViewModel is parent of SecondPageViewModel. You can see the event subscription in the SecondPageViewModel constructor.
A second implementation is FirstPageViewModel is on the same level of SecondPageViewModel. This uses a Mediator between the two ViewModels. It is basically removing the dependency of FirstPageViewModel from SecondPageViewModel
A third one would be to create your own delegate on FirstPageViewModel for SecondPageViewModel to subscribe on. It's basically the same thing as PropertyChanged, but you can configure what event arguments you are ready to pass.
Here's a demo:
public delegate void UsernameChangedEventHandler(string username);
public class FirstPageViewModel : INotifyPropertyChanged
{
// 3) Third implementation
public event UsernameChangedEventHandler UsernameChanged;
private string _username;
public string UserName
{
get { return _username; }
set
{
_username = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("UserName"));
if (UsernameChanged != null)
UsernameChanged(this.UserName);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class SecondPageViewModel : INotifyPropertyChanged
{
private string _username;
public string UserName
{
get { return _username; }
set
{
_username = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("UserName"));
}
}
public SecondPageViewModel()
{
}
public SecondPageViewModel(FirstPageViewModel parent)
{
// 1) First implementation
parent.PropertyChanged += FirstPageViewModel_OnPropertyChanged;
// 3) Third Implementation
parent.UsernameChanged += Parent_UsernameChanged;
}
private void Parent_UsernameChanged(string username)
{
this.UserName = username;
}
private void FirstPageViewModel_OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
FirstPageViewModel parent = (FirstPageViewModel) sender;
if(args.PropertyName.Equals("username", StringComparison.InvariantCultureIgnoreCase))
{
this.UserName = parent.UserName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class ParentViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private FirstPageViewModel _firstPageViewModel;
private SecondPageViewModel _secondPageViewModel;
public ParentViewModel()
{
// 2) Second implementation
_firstPageViewModel = new FirstPageViewModel();
_secondPageViewModel = new SecondPageViewModel();
_firstPageViewModel.PropertyChanged += FirstPageViewModel_PropertyChanged;
// 3) Third Implementation
_firstPageViewModel.UsernameChanged += FirstPageViewModel_UsernameChanged;
}
private void FirstPageViewModel_UsernameChanged(string username)
{
_secondPageViewModel.UserName = username;
}
private void FirstPageViewModel_PropertyChanged(object sender, PropertyChangedEventArgs args)
{
FirstPageViewModel firstPageViewModel = (FirstPageViewModel)sender;
if (args.PropertyName.Equals("username", StringComparison.InvariantCultureIgnoreCase))
{
_secondPageViewModel.UserName = firstPageViewModel.UserName;
}
}
}

Related

INotifyPropertyChanged does't work when field of property change internally

I try to binding textblock usercontrol with property of my class, but it only works at initial stage, I have implement IPropertyChnaged in my class.
In my class, _Feedbackpos (field of property) would change in background, I don't know how to solve this problem.
my class
public class TestControl : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyname)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
}
private double _Feedbackpos;
public double Feedbackpos
{
get
{
return _Feedbackpos;
}
set
{
_Feedbackpos = value;
NotifyPropertyChanged("Feedbackpos");
}
}
//it's a callback function, it would excute when detect feedback position of controller change
private void ReadFeedbackpos()
{
_Feedbackpos = Controller.Read();
}
}
application windows
TestControl TestDll = new TestControl();
Binding BindingTxtBlk = new Binding(){Source= TestDll, Path = new Property("Feedbackpos")};
FeedbackPosTxtBlk.Setbinding(Textblock.TextProperty,BindingTxtBlk);
Change the function ReadFeedbackpos() to
private void ReadFeedbackpos()
{
Feedbackpos = Controller.Read();
}
Otherwise NotifyPropertyChanged("Feedbackpos"); will never get called.

Refresh or update content page every few seconds automatically

I am using Xamarin.forms (PCL) and I need to refresh/update Content Page with its data every few seconds. The data is retrieved from API in the viewmodel.
Is there any method or handler that can be used periodically to call the Get Api periodically inside the page.xaml.cs, something like:
methodRunPeriodically()
{
userdata = await UserService.GetUserasync(_UserViewModel.EmployeeId);
}
Xamarin.Forms has an API for starting a timer that you might find useful for this, documented here.
Device.StartTimer (TimeSpan.FromSeconds(10), () => {
// If you want to update UI, make sure its on the on the main thread.
// Otherwise, you can remove the BeginInvokeOnMainThread
Device.BeginInvokeOnMainThread(() => methodRunPeriodically());
return shouldRunAgain;
});
Based on the code in the above question, you would ensure that:
Your userdata object implements IPropertyChange as follows:
//Other usings skipped for brevity
...
...
using System.ComponentModel;
using System.Runtime.CompilerServices;
// This is a simple user class that
// implements the IPropertyChange interface.
public class DemoUser : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private string userName = string.Empty;
private string phoneNumber = string.Empty;
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public DemoUser()
{
}
public string Id { get; set; }
public string UserName
{
get
{
return this.userName;
}
set
{
if (value != this.userName)
{
this.userName = value;
NotifyPropertyChanged();
}
}
}
public string PhoneNumber
{
get
{
return this.phoneNumber;
}
set
{
if (value != this.phoneNumber)
{
this.phoneNumber = value;
NotifyPropertyChanged();
}
}
}
}
In your ContentPage, you then try the following, (I slightly modified the code by others above):
public class UserPage : ContentPage
{
private DemoUser demoUser;
private int intervalInSeconds;
public UserPage()
{
//Assuming this is a XAML Page....
InitializeComponent();
}
public UserPage(DemoUser demoUser, int intervalInSeconds = 10) : this()
{
this.demoUser = demoUser;
this.intervalInSeconds = intervalInSeconds;
this.BindingContext = this.demoUser;
Device.StartTimer(TimeSpan.FromSeconds(this.intervalInSeconds), () =>
{
Device.BeginInvokeOnMainThread(() => refreshDemoUser());
return true;
});
}
private async void refreshDemoUser()
{
this.demoUser = await getDemoUserById(this.demoUser.Id);
}
}
You can do as follows to run a Task when 10 seconds has passed. Returning true in Device.StartTimer will ensure that the Timer keeps running. Also, you want to ensure that you invoke the method on the main thread to update the UI:
public MyConstructor()
{
StartTimer();
}
private void StartTimer()
{
Device.StartTimer(System.TimeSpan.FromSeconds(10), () =>
{
Device.BeginInvokeOnMainThread(UpdateUserDataAsync);
return true;
});
}
private async void UpdateUserDataAsync()
{
userdata = await UserService.GetUserasync(_UserViewModel.EmployeeId);
}
If your API doesn't expose an EventHandler that you can subscribe to, then you need to do as mentioned in my example above.
You should just bind the UI to properties in your ViewModel and then set those properties appropriately. Calling OnPropertyChanged() will trigger Xamarin.Forms to update the UI based on the bound properties. Something like below:
//Code in Page
public class MyPage : ContentPage
{
public MyPage()
{
var entry = new Entry();
BindingContext = new MyViewModel();
entry.SetBinding<MyViewModel>(Entry.TextProperty, vm=>vm.EntryText);
Content = entry;
}
}
//Code in ViewModel
public class MyViewModel() : INotifyPropertyChanged
{
public MyViewModel()
{
Task.Factory.StartNew(()=> methodRunPeriodically());
}
string entryText;
public string EntryText
{
get { return entryText; }
set
{
if(entryText == value)
return;
entryText = value;
OnPropertyChanged();
}
}
bool shouldRun = true;
async Task methodRunPeriodically()
{
while(shouldRun)
{
userdata = await UserService.GetUserasync(_UserViewModel.EmployeeId);
EntryText = userdata.FirstName;
await Task.Delay(5000); //Run this every 5 seconds
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In this pattern, we are kicking off a long-running task that will run in a loop. It is reaching out to refresh the userData every 5 seconds and then setting the EntryText property. In the setter of the EntryText property in our ViewModel, we are calling OnPropertyChanged() which will cause Xamarin.Forms to update the UI. Calling OnPropertyChanged() triggers Xamarin.Forms to switch thread context from the background task to the UI thread and then back to the background task.
I didn't write this in XAML, but the binding would be pretty much the same except the entry would be like below:
<Entry Text={Binding EntryText}/>
EDIT
#therealjohn's answer is good also. You could use that instead of my while loop like below:
bool shouldRun = true;
methodRunPeriodically()
{
Device.StartTimer(TimeSpan.FromSeconds(5), () =>
{
userdata = await UserService.GetUserasync(_UserViewModel.EmployeeId);
EntryText = userdata.FirstName;
return shouldRun;
});
}
You can review what the Forms source code is doing with the Device.StartTimer on the native iOS and Android.
Update UI every one second:
Device.StartTimer(TimeSpan.FromMilliseconds(1000), loop2);
bool loop2()
{
Device.BeginInvokeOnMainThread(() => updateUI());
return true;
}
or:
Device.StartTimer(TimeSpan.FromMilliseconds(1000), loop2);
bool loop2()
{
Device.BeginInvokeOnMainThread(() => {
updateUI();
//more stuff;
});
return true;
}

I need a way to assign and retrieve values between windows form (C#)

So, I'm doing a school project atm. The application needs to be able to calculate the area of squares, circles etc.
I have one form for each figure to calculate the area off. Right now I have a "main menu" and three forms (one for each figure) and I want to be able to assign a variable LatestResult within one form and access it from the main menu form.
Note: I want to be able to do this without "loading the variable into the new form" like this: Form1 FormMainMenu = new Form1(LatestResult)
I've been trying to work with Get & Set in my Variables.cs class, but I can't seem to make it work right, if it's possible to do it with this.
EDIT: Put my code in the post
My Variables.cslooks like this:
public static string latestresult2
{
get
{
return latestresult2;
}
set
{
latestresult2 = value;
}
}
And then I assign the value upon a button click in one form:
Variables.latestresult2 = breddeR + " * " + længdeR + " = " + resultat;
breddeR and længdeR are the int variables for my calculation and resultat is the result.
At last I try to do this in another form:
label1.Text = Variables.latestresult2;
EDIT 2:
From my MainView form
private void button1_Click(object sender, EventArgs e)
{
Form2 FormTrekant = new Form2();
FormTrekant.Show();
}
You may use the INotifyPropertyChanged interface to facilitate this. This interface works with both WinForms and WPF.
public class Form2 : Form, INotifyPropertyChanged
{
public string latestresult2;
public event PropertyChangedEventHandler PropertyChanged;
public string LatestResult2
{
get
{
return latestresult2;
}
set
{
latestresult2 = value;
this.OnPropertyChanged("LatestResult2");
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler == null)
{
return;
}
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Form3 : Form, INotifyPropertyChanged
{
public string latestResult3;
public event PropertyChangedEventHandler PropertyChanged;
public string LatestResult3
{
get
{
return latestresult3;
}
set
{
latestresult3 = value;
this.OnPropertyChanged("LatestResult3");
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler == null)
{
return;
}
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The INotifyPropertyChanged interface allows you to subscribe to another objects property changes. In the above code, the second form will raise the event when ever its LatestResult2 property has had its value changed.
Now you just have your Form1 subscribe to it.
public class MainForm : Form
{
private Form2 secondaryForm;
private Form3 thirdForm;
public string LatestValue {get; set;}
public void Form2ButtonClick() // Assume this happens in a button click event on MainWindow.
{
this.secondaryForm = new Form2();
this.secondaryForm.PropertyChanged += this.LatestValueChanged;
this.secondaryForm.Closing += this.ChildWindowClosing;
}
public void Form3ButtonClick() // Assume this happens in a button click event on MainWindow.
{
this.thirdForm = new Form3();
this.thirdForm.PropertyChanged += this.LatestValueChanged;
this.thirdForm.Closing += this.ChildWindowClosing;
}
private void LatestValueChanged(object sender, PropertyChangedEventArgs e)
{
// Do your update here.
if (sender == this.secondaryForm)
{
this.LatestValue = this.secondaryForm.LatestValue2;
}
else if (sender == this.thirdForm)
{
this.LatestValue = this.thirdForm.LatestValue3;
}
}
// Clean up our event handlers when either of the children forms close.
private void ChildWindowClosing(object sender, ClosingWindowEventHandlerArgs args)
{
if (sender == this.secondaryForm)
{
this.secondaryForm.Closing -= this.ChildWindowClosing;
this.secondaryForm.PropertyChanged -= this.LatestValueChanged;
}
else if (sender == this.thirdForm)
{
this.thirdForm.Closing -= this.ChildWindowClosing;
this.thirdForm.PropertyChanged -= this.LatestValueChanged;
}
}
}
Your MainWindow can now react to changes within Form2, without having to pass values around. One thing to note, is that you will want to unsubscribe from the event when the Form2 is closed. Otherwise you will leak.
You can specify the instance of the main view to the other view. This way, you can access the properties of the main view.
Some code to explaain;
public class MainView
{
public string LatestResult { get; set; }
}
public class ChildView
{
private readonly MainView MainView;
public ChildView(MainView mainView)
{
this.MainView = mainView;
}
public void Calculation()
{
//Some calculation
this.MainView.LatestResult = "Some result";
}
}
Now this code can be used like this:
var mainView = new MainView();
var childView = new ChildView(mainView);
childView.Calculation();
//mainView.LatestResult == "Some result"

How to propagate property change notifications of objects within collections

Lets say I have classes like this
public class R
{
protected string name;
protected List<S> listOfObjectS;
}
public class S
{
private string name, ID;
private A objectA;
}
public class A
{
private string name;
private int count;
}
If a user has two views open, one displaying instances of R and another allowing users to modify an instance of A, I need the view of R to change when the user changes any instance of A.
If the user changes a property of an instance of A, what is the best way to propagate that change (through instances of S) so that all instances of R display the new state of A?
EDIT: Overhauling this answer to be more specific to the question since the tags show you already knew about INotifyPropertyChanged.
You need to implement INotifyPropertyChanged in class A and in class S. Make it so objectA can only be set through a property that will raise the PropertyChanged event on S whenever a property is changed in A. Example:
public class A : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set { name = value; OnPropertyChanged("Name"); }
}
private int count;
public int Count
{
get { return count; }
set { count = value; OnPropertyChanged("Count"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
... and class S...
public class S : INotifyPropertyChanged
{
private string name, ID;
private A objectA;
public A ObjectA
{
get { return objectA; }
set
{
var old = objectA;
objectA = value;
// Remove the event subscription from the old instance.
if (old != null) old.PropertyChanged -= objectA_PropertyChanged;
// Add the event subscription to the new instance.
if (objectA != null) objectA.PropertyChanged += objectA_PropertyChanged;
OnPropertyChanged("ObjectA");
}
}
void objectA_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Propagate the change to any listeners. Prefix with ObjectA so listeners can tell the difference.
OnPropertyChanged("ObjectA." + e.PropertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
For class R, use ObservableCollection<S> instead of List<S>, and subscribe to its CollectionChanged event, and monitor when objects are added or removed to listOfObjectS. When they are added, subscribe to S's PropertyChanged events. Then updated R's view. Example:
public class R
{
protected string name;
protected System.Collections.ObjectModel.ObservableCollection<S> ListOfObjectS { get; private set; }
public R()
{
// Use ObservableCollection instead.
ListOfObjectS = new ObservableCollection<S>();
// Subscribe to all changes to the collection.
ListOfObjectS.CollectionChanged += listOfObjectS_CollectionChanged;
}
void listOfObjectS_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
// When items are removed, unsubscribe from property change notifications.
var oldItems = (e.OldItems ?? new INotifyPropertyChanged[0]).OfType<INotifyPropertyChanged>();
foreach (var item in oldItems)
item.PropertyChanged -= item_PropertyChanged;
}
// When item(s) are added, subscribe to property notifications.
if (e.Action == NotifyCollectionChangedAction.Add)
{
var newItems = (e.NewItems ?? new INotifyPropertyChanged[0]).OfType<INotifyPropertyChanged>();
foreach (var item in newItems)
item.PropertyChanged += item_PropertyChanged;
}
// NOTE: I'm not handling NotifyCollectionChangedAction.Reset.
// You'll want to look into when this event is raised and handle it
// in a special fashion.
}
void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName.StartsWith("ObjectA."))
{
// Refresh any dependent views, forms, controls, whatever...
}
}
}
Let's say you have a form1 where you use an instance of class R to display a list of instances from class A. You than press edit and you send the instance of that same class A from the class R instance towards the new form.
This will than be a reference to the object contained in the instance of R and therefore be updated within form2. The only thing you than have to do is refresh the instance of class A in the list of form1.
To explain: when you are calling a form or method with the an object instance of a class, this will create a reference, not a clone and therefore can be updated from the second form2.

How to intercept NotifyPropertyChange event

I just recently discovered an INotifyPropertyChange interface. I managed to implement this interface in my clss and everything works fine. However I was wondering if it is possible to intercept this event in code and fire a function
Let's say that I have a function
DoStuff()
and I wan't to fire this function everytime property1, property2 or property3 changes.
Of course I could put this function in set block in my class but this is not a good idea(I think).
If you mean to internal method that'll handle this event you can do it by registering to the event in the class constructor. For example:
public class AnswerViewModel : IAnswerViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private string content;
public AnswerViewModel()
{
PropertyChanged += (sender, args) => DoStuff();
}
public string Content
{
get { return content; }
set
{
content = value;
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public void DoStuff()
{
// this method will be called whenever PropertyChanged event raised
}
}
If the intercepting method belongs to other class:
public class PropertiesInterceptor
{
private readonly AnswerViewModel viewModel;
private readonly List<string> propertiesToIntercept =
new List<string> { "property1", "property2", "property3" };
public PropertiesInterceptor(AnswerViewModel viewModel)
{
this.viewModel = viewModel;
viewModel.PropertyChanged += OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (propertiesToIntercept.Contains(args.PropertyName))
{
DoStuff();
}
}
private void DoStuff()
{
// Do something with viewModel
}
}
Intercept the PropertyChanged Event:
http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.propertychanged.aspx
You could fire the method from a RaisePropertyChanged() method:
public int Property1
{
get { return this.property1; }
set
{
if (this.property1 != value)
{
this.property1 = value;
RaisePropertyChanged("Property1");
}
}
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
DoStuff(); // Call DoStuff here.
}
Stealing Elisha's answer to answer your question in Merlyn's answer
public class AnswerViewModel : IAnswerViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private string property1;
private string property2;
private string propertyX;
public AnswerViewModel()
{
PropertyChanged += (sender, args) =>
{
if(args.PropertyName == "Property1" || args.PropertyName == "Property2")
DoStuff();
}
}
public string Property1
{
get { return content; }
set
{
property1 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Property1"));
}
}
public string Property2
{
get { return content; }
set
{
property2 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Property2"));
}
}
public string PropertyX
{
get { return content; }
set
{
propertyX = value;
PropertyChanged(this, new PropertyChangedEventArgs("PropertyX"));
}
}
public void DoStuff()
{
// this method will be called whenever PropertyChanged event raised from Property1 or Property2
}
}
If the class DoStuff is in is a member you can do
private otherClass
public AnswerViewModel()
{
PropertyChanged += (sender, args) =>
{
if(args.PropertyName == "Property1" || args.PropertyName == "Property2")
otherClass.DoStuff();
}
}
Otherwise you can just have otherClass register a event on its own in your main code.
Did you need it to replace the existing NotifyPropertyChanged event handlers, or just get called when NotifyPropertyChanged is called?
If you mean the second, you can simply register an event handler
edit
You can add an event handler that gets called on NotifyPropertyChanged, checks if the property parameter is equal to Property1, Property2, or Property3, and only then forwards it to the actual function you want to call.

Categories