What I am trying to do: pass the data from ViewModel to Activity
ViewModel.cs
public event EventHandler RecommendedScents;
private void _recommendedScents()
{
var handler = RecommendedScents;
if (handler != null)
handler(this, new System.EventArgs());
}
Activity.cs
I register the Event in view
ViewModel.RecommendedScents -= SetRecommendedScents;
ViewModel.RecommendedScents += SetRecommendedScents;
Get the Control here:
private void SetRecommendedScents(object sender, EventArgs e)
{
}
You don't need to use such trick to pass data from ViewModel to View. Just declare your View as MvxActivity<MyViewModel>
And you will have a property ViewModel in your View.
[Activity(ScreenOrientation = ScreenOrientation.Portrait)]
public class MyView : MvxActivity<MyViewModel>
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
var myValue = ViewModel.SomeProperty; // here you access your VM
}
}
If you need to send data from ViewModel to View, you need to use a a Event aggregator system like MvvmCross.Plugin.Messenger
https://www.mvvmcross.com/documentation/plugins/messenger?scroll=1524
Related
In version 5 of MvvmCross, there has been added an asynchronous Initialize override where you can do you heavy data loading.
public override async Task Initialize()
{
MyObject = await GetObject();
}
Is there a way to determine in the View that the Initialize has completed? Say in the View I want to set the Toolbar Title to a display a field in MyObject
MyViewModel vm;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your application here
this.SetContentView(Resource.Layout.MyView);
var toolbar = (Toolbar)FindViewById(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
vm = (MyViewModel)this.ViewModel;
SupportActionBar.Title = vm.MyObject.Name;
}
On the line that sets the SupportActionBar.Title, is there a way to know for sure whether the Initialize task has completed and if not, get notified when it does?
UPDATE:
I tried set two correct answers because #nmilcoff answered my actual question and #Trevor Balcom showed me a better way to do what I wanted.
Yes, you can subscribe to InitializeTask's property changes.
Something like this will work:
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// your code
ViewModel.PropertyChanged += MyViewModel_PropertyChanged;
}
private void MyViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(ViewModel.InitializeTask) && ViewModel.InitializeTask != null)
{
ViewModel.InitializeTask.PropertyChanged += ViewModel_InitializeTask_PropertyChanged;
}
}
private void ViewModel_InitializeTask_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(ViewModel.InitializeTask.IsSuccessfullyCompleted))
SupportActionBar.Title = ViewModel.MyObject.Name;
}
Of course, it could be the case that it might be easier to just listen to ViewModel.MyObject.Name property changes. But the above is a generic way to listen to InitializeTask property changes.
You can learn more about InitializeTask and MvxNotifyTask in the official documentation.
On Xamarin Forms:
I wanted to add Property Changed event login in the VM to be able to test it, so:
View.xaml.cs
protected override void OnViewModelSet()
{
base.OnViewModelSet();
var vm = this.DataContext as SearchMovieViewModel;
if (vm is null)
{
return;
}
vm.OnViewModelSet();
}
On your ViewModel:
/// <summary>
/// This method should be called in every View Code Behind when you
/// need to subscribe to InitializeTask changes.
/// </summary>
public void OnViewModelSet()
{
if (this.InitializeTask is null)
{
return;
}
this.InitializeTask.PropertyChanged += this.InitializeTask_PropertyChanged;
}
Finally on your View Model implement whatever check you need to do for MvvmCross InitializeTask, in my case I used IsCompleted Property, but you can use whichever you need:
private void InitializeTask_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(this.InitializeTask.IsCompleted))
{
// do something
}
}
DonĀ“t forget to unsubscribe, for example when the view is destroyed. You can override this method in your View Model:
public override void ViewDestroy(bool viewFinishing = true)
{
base.ViewDestroy(viewFinishing);
this.InitializeTask.PropertyChanged -= this.InitializeTask_PropertyChanged;
}
The Toolbar also supports data binding the Title property like so:
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/AppTheme.PopupOverlay"
app:MvxBind="Title MyObject.Name" />
I am trying to get the text of a label to update on the front of end of my app.
At the moment Im using Message Centre to send a notification up to the view model and increment a number that should update on the label in the view.
Im using Xamarin Forms and PCL.
I can get the number to log out in the debug so I know the message centre is working. But its not updating the view.
the relevant Xaml:
<Label Text="{Binding counter}"
Grid.Row="0"/>
The code behind:
public partial class DriverDashboardView : ContentPage
{
private DriverDashboardViewModel driverdashboardviewmodel;
public DriverDashboardView()
{
InitializeComponent();
this.Title = "Driver's Dashboard";
BindingContext = driverdashboardviewmodel = new DriverDashboardViewModel();
dataList.ItemTapped += DataList_ItemTapped;
}
private void DataList_ItemTapped(object sender, ItemTappedEventArgs e)
{
DisplayAlert("Route Information","Various Data","OK");
}
protected async override void OnAppearing()
{
base.OnAppearing();
await driverdashboardviewmodel.GetLabelInfo();
}
}
The View Model:
public class DriverDashboardViewModel:BaseViewModel,INotifyPropertyChanged
{
private int messageCounter { get; set; }
public string counter { get { return messageCounter.ToString(); }
set {
if (Equals(value, messageCounter)) return;
messageCounter = Convert.ToInt32(value);
OnPropertyChanged(nameof(counter));
} }
public DriverDashboardViewModel()
{
MessagingCenter.Subscribe<App>((App)Application.Current, "Increase", (variable) => {
messageCounter++;
});
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
And the relevant section that implements the message centre:
Foregroundmessages.cs:
MessagingCenter.Send((App)Xamarin.Forms.Application.Current, "Increase");
As stated the messaging centre works fine. It gets as far as the view model but doesnt update the counter variable to the view. I have tried setting the counter as an int and a string hence the conversion in the get and set.
I also tried observable collection but that seemed redundant because its a single variable not a collection or list.
Any ideas?
your code is updating the private messageCounter property, not the public counter property that you are binding to. Updating messageCounter does not cause PropertyChanged to fire.
MessagingCenter.Subscribe<App>((App)Application.Current, "Increase", (variable) => {
messageCounter++;
});
I have an async method. I am trying to bind listview from a json file on web.
I am developing an Universal Windows Platform App. The list view is being loaded when I open the page on second time. But off course I want list view loaded on first load. What can cause the problem for it? Thanks.
This is the codebehind of my XAML.
public MyPage()
{
this.InitializeComponent();
Namedays = new List<NamedayModel>();
LoadData();
listview1.ItemsSource = Namedays;
}
public async void LoadData()
{
Namedays = await GetAllNamedaysAsync();
}
private static List<NamedayModel> allNamedaysCache;
public static async Task<List<NamedayModel>> GetAllNamedaysAsync()
{
if (allNamedaysCache != null)
return allNamedaysCache;
var client = new HttpClient();
var stream = await client.GetStreamAsync("http://www.example.com/myfile.json");
var serializer = new DataContractJsonSerializer(typeof(List<NamedayModel>));
allNamedaysCache = (List<NamedayModel>)serializer.ReadObject(stream);
return allNamedaysCache;
}
The are couple of things you make wrong:
don't make async void if not really needed (for example events),
your are running your LoadData() in constructor as fire-forget, it doesn't await (it's also impossible in constructor), the code goes further and you set listview's itemssource to Namedays. You are not using binding for ItemsSource, thus when you change Namedays in loaded method, it's not reflected by listview.
apart from that, also by asigning new value to Namedays in LoadData(), you don't change the value of listview's itemssource - it's still pointing to old collection.
Much better in this case would be if you had used binding for ItemsSource, define property and load data asynchronously in for example Loaded event. Sample code, XAML:
<ListView ItemsSource="{Binding Namedays}"/>
And the code behind:
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseProperty(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
private List<NamedayModel> namedays = new List<NamedayModel>();
public List<NamedayModel> Namedays { get { return namedays; } set { namedays = value; RaiseProperty(nameof(Namedays)); } }
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
this.Loaded += MainPage_Loaded;
}
private async void MainPage_Loaded(object sender, RoutedEventArgs e)
{
Namedays = await GetAllNamedaysAsync();
}
public static async Task<List<NamedayModel>> GetAllNamedaysAsync()
{
if (allNamedaysCache != null)
return allNamedaysCache;
var client = new HttpClient();
var stream = await client.GetStreamAsync("http://www.tyosoft.com/namedays_hu.json");
var serializer = new DataContractJsonSerializer(typeof(List<NamedayModel>));
allNamedaysCache = (List<NamedayModel>)serializer.ReadObject(stream);
return allNamedaysCache;
}
}
As a side note - you may also think of using ObservableCollection instead of List if you want to modify it without reassigning.
Your listview1 does not know about the data so it does not load them. When you open the page for second time, data is already loaded and listview1 can display them.
You need to implement INotifyPropertyChanged interface like so:
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
and then call OnPropertyChanged(nameof(Namedays)) after you load your data
Why does my textbox fail to update when I try to update it from another class?
I've instantiated the MainWindow class in my Email class, but when I try to do
main.trending.Text += emailText;
Am I doing something wrong?
You should bind your data.
Model
public class YourData : INotifyPropertyChanged
{
private string _textBoxData;
public YourData()
{
}
public string TextBoxData
{
get { return _textBoxData; }
set
{
_textBoxData = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("TextBoxData");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
XAML Binding
Set data context in Codebehind
this.DataContext = YourData;
Bind Property
<TextBox Text="{Binding Path=Name2}"/>
See #sa_ddam213 comment. Dont do something like MainWindow main = new MainWindow(); inside Email class. Instead, pass the MainWindow object you already have.
Following codes will work:
public class MainWindow
{
public void MethodWhereYouCreateEmailClass()
{
Email email = new Email;
email.Main = this;
}
}
public class Email
{
public MainWindow main;
public void MethodWhereYouSetTrendingText()
{
main.trending.Text += emailText;
}
}
But I dont say that is best practice. I just try to keep it close to your existing code i guess.
I have a custom control in C# WinForms called BaseControl and there I have a property called Selected.
I want to have an event SelectedChanged and virtual method OnSelecteChanged in the base control and they should behave in the same manner as we have in Control class for many properties i.e. Click event and OnClick method.
Means anyone who derives from my BaseControl can either bind to the event or can override the OnSelectedChanged method.
So, when the value of Selected property is changed event should be fired and if the method is overridden control should go to that method.
I know how to fire the event but don't know how to do it for method.
Please guide me...
Below is an example of how events should be implemented:
public class BaseControl : Control
{
private object _selected;
public object Selected
{
get { return _selected; }
set
{
if (!Equals(_selected, value))
{
_selected = value;
OnSelectedChanged(EventArgs.Empty);
}
}
}
public event EventHandler SelectedChanged;
protected virtual void OnSelectedChanged(EventArgs e)
{
if (SelectedChanged != null)
SelectedChanged(this, e);
}
}
With this example, you can override OnSelectedChanged in an overriden class, like this:
public class MyControl : BaseControl
{
protected override void OnSelectedChanged(EventArgs e)
{
base.OnSelectedChanged(e);
// My own logic.
}
}
private bool _selected;
public bool Selected
{
get { return _selected; }
set
{
if (value != _selected)
{
_selected = value;
OnSelectedChanged();
}
}
}
public event EventHandler SelectedChanged;
protected virtual void OnSelectedChanged()
{
var handler = SelectedChanged;
if (handler != null)
handler(this, EventArgs.Empty);
}
Basically you don't fire the event from your Selected property setter - you call the method, and make the method call the event. Anyone overriding the method should call base.OnSelectedChanged to make sure the event still fires. So your method should look something like this:
protected virtual void OnSelectedChanged(EventArgs e) {
EventHandler handler = Selected; // Or your own delegate variable
if (handler != null) {
handler(this, e);
}
}