Notes: Xamarin 4.2.1.64, Visual Studio 2015 proff.
I have created a cross platform app that is to work on a android device that scans barcodes.
Currently When scanned the software has an optional output mode, (buffer, keyboard,clipboard and intent).
Currently using keyboard mode.
Flow
User clicks device button scanning a barcode, software attempts to dump to an input on the screen, if not it instead tabs (app is set focus to entry field on startup). When a button on the app is clicked it calls my service to query a set of data and return the results, the list of results is then updated for the user to see.
Flow i need to change
User clicks device button scanning a barcode, only this time device is set to intent and broadcasts, my app reciever picks up the broadcast, reads the barcode from the intent and calls my viewmodel to update the field with the barcode. the viewmodel will now change to detect field change and run the method accordingly.
Code so far
Portable Xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="StockCheckApp.Views.ScannerPage"
xmlns:ViewModels="clr-namespace:StockCheckApp.ViewModels;assembly=StockCheckApp">
<Label Text="{Binding MainText}" VerticalOptions="Center" HorizontalOptions="Center" />
<StackLayout Orientation="Vertical">
<Entry x:Name="myBox" Text="{Binding UserInput, Mode=TwoWay}" />
<Button Text="Check Stock" Command="{Binding PostCommand}"/>
<ListView ItemsSource="{Binding StockList}"
HasUnevenRows="True">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Vertical" Padding="12,6">
<Label Text="{Binding St_Code}" FontSize="24" />
<Label Text="{Binding St_Desc}" />
<Label Text="{Binding Sl_Loc}" />
<Label Text="{Binding Sa_Datetime}" />
<Label Text="{Binding Sa_Qty}" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
Portable Xaml Code behind
public partial class ScannerPage : ContentPage
{
public ScannerPage()
{
InitializeComponent();
BindingContext = new MainViewModel(this);
}
protected override void OnAppearing()
{
base.OnAppearing();
myBox.Focus();
}
public Entry MyBox
{
get
{
return myBox;
}
}
}
Portable Main view model
public class MainViewModel : INotifyPropertyChanged
{
ScannerPage page;
private List<Stock> _stockList;
private string _userInput;
public List<Stock> StockList
{
get { return _stockList; }
set
{
_stockList = value;
OnPropertyChanged();
}
}
public string UserInput
{
get { return _userInput; }
set
{
_userInput = value;
OnPropertyChanged();
}
}
public MainViewModel(ScannerPage parent)
{
page = parent;
page.MyBox.Focus();
}
public Command PostCommand
{
get
{
return new Command(async () =>
{
var stockService = new StockService();
StockList = await stockService.GetStockAsync(UserInput);
page.MyBox.Focus();
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
//use this to clear? then reset focus?
}
}
Android reciever class
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new[] { "android.intent.action.bcr.newdata" })]
public class Receiver1 : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
Toast.MakeText(context, "Received intent!", ToastLength.Short).Show();
Services.StockService meh = new Services.StockService();
//MainViewModel md = new MainViewModel();
Dowork(meh, intent);
}
//this isnt correct i need to update viewmodel not call service direct!
private async void Dowork (Services.StockService meh, Intent intent)
{
string action = intent.Action.ToString();
string decodedBarcode = intent.GetStringExtra(BCRIntents.ExtraBcrString);
//now i just need to update my field in the xaml....somehow
}
}
What I'm stuck on
I step through and my code hits my breakpoints, but at this stage I need my reciver to somehow update the entry field.
I'm not familiar with Xamarin yet and I'm learning alot, so I realize this may actually be a simple answer.
Also
Am I correct in what I intend to do? recieve the barcode number and access the viewmodels "userinput" property and change it? os should I somehow access the field on the view and change it instead then allow my property changed method to carry out business logic?
You could use the Messaging Center that ships with Xamarin.Forms
https://developer.xamarin.com/guides/xamarin-forms/messaging-center/
Have your ViewModel Subscribe to the MessagingCenter for an event coming from your service, then use the event to update the property that is bound to the field. If you need a type in your PCL to map the subscribe to, create an interface for your service in the PCL that doesn't need to have any actual method contracts, then have your service implement it so you can set up your subscribe with strong types:
// in your PCL
public interface IScanReceiver
{
}
// in your android project
public class Receiver1 : BroadcastReceiver, IScanReceiver
// in your viewmodel
MessagingCenter.Subscribe<IScanReceiver>();
Alternatively, you could set up your ViewModels in a dependency service and then use the Service Locator (anti)pattern to find your ViewModel instance and update the property that is bound to the field.
MVVM Light: http://www.mvvmlight.net/ does a great job at giving you tools to do this.
Related
I have a Tab Control, with an "Exams" tab and a "Templates" tab. I want to share information between the two tabs.
My background is mostly in web with React and Redux, so my instinct is to have the shared information (of model type Exam) belong to the MainWindow, probably as a window resource.
My tech lead, who is much more experienced than me -- in all things, but also in C# though he's also new to WPF), assures me that this is not the way to do it in this setting, that the tabs' ViewModels should be sharing/passing the information.
So I've set up my basic UI with a text box in the Exams tab and a label to display the contents of the text box in the Templates tab.
<Page x:Class="Gui.Tabs.ExamsTab.ExamsHome" Title="ExamsTab">
<Grid VerticalAlignment="Center">
<StackPanel>
<Label Content="EXAMS TAB" />
<TextBox Text="{Binding ExamString, UpdateSourceTrigger=PropertyChanged}"/>
<Label Content="{Binding ExamString}" />
</StackPanel>
</Grid>
</Page>
--------
<Page x:Class="Gui.Tabs.TemplatesTab.TemplatesHome" Title="TemplatesHome">
<Grid VerticalAlignment="Center">
<StackPanel>
<Label Content="TEMPLATES TAB"/>
<Label Content="Text from Exams tab:"/>
<Label BorderBrush="Red" BorderThickness="1" Content="{Binding StringFromExamsTab}"/>
</StackPanel>
</Grid>
</Page>
And I've wired up the ViewModels best I can: (the code-behind and the viewmodels are in the same file for now)
namespace Gui.Tabs.ExamsTab {
public partial class ExamsHome : Page {
public ExamsHome() {
InitializeComponent();
DataContext = ViewModel;
}
public static readonly ExamsTabViewModel ViewModel = new ExamsTabViewModel();
}
public class ExamsTabViewModel :INotifyPropertyChanged {
private string _examString = "Default exam string";
public string ExamString {
get => _examString;
set {
_examString = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
------
namespace Gui.Tabs.TemplatesTab {
public partial class TemplatesHome : Page {
public TemplatesHome() {
InitializeComponent();
DataContext = viewModel;
}
public static readonly TemplatesTabViewModel viewModel = new TemplatesTabViewModel();
}
public class TemplatesTabViewModel {
public string StringFromExamsTab {
// needs to be notified of value change!
get => ExamsHome.ViewModel.ExamString;
}
}
}
In the Exams tab, the text-box and its corresponding label are synced up.
And, in the Templates tab, the red box says "Default exam string", but it doesn't change with the value in the text-box.
I've read that:
The XAML binding engine listens for [the PropertyChangedEvent] for each bound property on classes that implement the INotifyPropertyChanged interface
Which implies to me that as long as INotifyPropertyChanged is implemented in the Exams VM, the bindings in the Templates tab should be notified.
How can I make this work? Open to the possibility that I'm doing it entirely (or mostly) wrong.
There are tons and tons of articles around the internet about this topic, but I just can't wrap my head around it. Most articles use code behind, but I want to stick to "pure" MVVM since I try to learn it. Also, I explicitly don't want to use any other framework (MVVMlight, Ninject...). I just want to stick to what WPF has to offer. I know this got asked a lot, but what I found either was not mvvm or was not specific enough.
My task is simple: I want to see the most simple solution of opening a modal dialog, send it a string, and get a string from the dialog back upon closing it.
Therefore I set up my MainWindow.xaml with a text input field (TextBox), a button (that should open the modal dialog) and a textblock that will show the message I intend to receive from the dialog.
The dialog has a TextBlock, showing the user-input from MainWindow.xaml, and a TextBox to enter some text, and a button. You guessed it: you press the button, and the message I typed into the textfield get's returned to MainWindow.xaml. Please refer also to the images I've included - I think it's pretty self-explanatory.
MainWindow.xaml
<Window x:Class="Dialogs.MainWindow"
...
Title="First View (Main Window)" Height="240" Width="630">
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Main View sayz: "/>
<TextBox Width="360" Margin="10,0,0,30"/>
</StackPanel>
<Button Content="Send to Second View" Command="{Binding SendToSecondViewCommand}" Width="200"/>
<StackPanel Orientation="Horizontal" Margin="10,30,10,10">
<TextBlock Text="Second View replies: "/>
<TextBlock Width="360"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
SecondView.xaml
<UserControl x:Class="Dialogs.SecondView"
...
d:DesignHeight="240" d:DesignWidth="630" Background="BlanchedAlmond">
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="This is what First View sayz: "/>
<TextBlock Width="360"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="Second View replies: "/>
<TextBox Width="360" Margin="10,0,0,30"/>
</StackPanel>
<Button Content="Reply to First View" Command="{Binding ReplyToFirstViewCommand}" Width="200"/>
</StackPanel>
</Grid>
</UserControl>
Here is how I implemented INotifyPropertyChanged (It's actually a .cs file named BaseClasses; I know it's not named properly...)
public abstract class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged<T>(ref T variable, T value,
[CallerMemberName] string propertyName = null)
{
variable = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
And here my base class for relay commands:
public class CommandDelegateBase : ICommand
{
public delegate void ExecuteDelegate(object parameter);
public delegate bool CanExecuteDelegate(object paramerter);
private ExecuteDelegate execute;
private CanExecuteDelegate canExecute;
public CommandDelegateBase(ExecuteDelegate _execute, CanExecuteDelegate _canExecute = null)
{
execute = _execute;
canExecute = _canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return canExecute?.Invoke(parameter) ?? true;
}
public void Execute(object parameter)
{
execute.Invoke(parameter);
}
}
Lastly my ViewModels:
FirstViewModel:
public class FirstViewViewModel: NotifyPropertyChangedBase
{
private string _sendText;
public string SendText
{
get { return _sendText; }
set
{
_sendText = value;
OnPropertyChanged(ref _sendText, value);
}
}
public ICommand SendToSecondViewCommand { get; set; }
public FirstViewViewModel()
{
SendToSecondViewCommand = new CommandDelegateBase(SendExecuteCommand, SendCanExecuteCommand);
}
private bool SendCanExecuteCommand(object paramerter)
{
return true;
}
private void SendExecuteCommand(object parameter)
{
//Do stuff to :
// a) show the second view as modal dialog
// b) submit what I just wrote (SendText)
}
}
SecondViewModel:
public class SecondViewViewModel : NotifyPropertyChangedBase
{
private string _replyText;
public string ReplyText
{
get { return _replyText; }
set
{
_replyText = value;
OnPropertyChanged(ref _replyText, value);
}
}
public ICommand ReplyToFirstViewCommand { get; set; }
public SecondViewViewModel()
{
ReplyToFirstViewCommand = new CommandDelegateBase(ReplyExecuteCommand, ReplyCanExecuteCommand);
}
private bool ReplyCanExecuteCommand(object paramerter)
{
return true;
}
private void ReplyExecuteCommand(object parameter)
{
//Do stuff to :
// a) close the second view
// b) reply what I just wrote (ReplyText) back to First View.
}
}
I have a folder called "Models" in my solution but for the sake of simplicity it's empty.
I know there are solutions with helper classes or services - what ever pertains mvvm will do. I also do know that doing this for such a simple task as what I want is quiet "overkill", and has a lot more writing code coming with it than it would be justifyable for this purpose. But again: I'd like to learn this, and understand what I am doing.
Thank you so much in advance!
I wrote an article about this subject and provided a library and sample application. The article itself is long...because it's not a trivial topic...but causing a dialog box to appear can be as simple as this:
this.Dialogs.Add(new CustomDialogBoxViewModel()); // dialog box appears here
UPDATE: I just noticed that my MvvmDialogs library in that package is actually referencing MvvmLite. That's a vestigial remnant from when I was developing it though, the library itself doesn't need it, so you can remove the reference altogether.
Finding an MVVM pure solution to a programming problem, which may be straightforward in other contexts, is often not a simple task. However, creating a library of helper classes is a "write once, use many times" scenario, so no matter how much code is required, you don't have to reproduce it for every usage.
My preferred method for handling message dialogs in MVVM is a two part service module.
The View registers its data context (its ViewModel) with the DialogService as potentially wanting to display a dialog - the service will use the View's UI context to do so when it does.
The ViewModel calls the injected dialog service each time a dialog should be displayed. Calls to the MessageDialog service are made using the async / await pattern, rather than requiring some other form of callback in the ViewModel.
So now, displaying a MessageDialog from a ViewModel is as simple as
await _dialogService.ShowMessageAsync(this, "Hello from the dialog service.", perDialogIcon.Information, "Mvvm Dialog Service").ConfigureAwait(false);
or
var response = await _dialogService.ShowDialogAsync(this, perDialogButton.YesNo, "Do you want to continue?", perDialogIcon.Question, "Mvvm Dialog Service").ConfigureAwait(false);
I covered this in more detail on a blog post.
As an aside, your ViewModel properties look a bit wierd - you're setting the backing-field value, then passing it into your OnPropertyChanged() method where the value is set again.
I'm currently building the UI for the app I'm working on and I have a few problems with the bindings.
The Scenario:
I have a pivot control with every pivot element consisting of an extra Frame/Page.
Now I have a TextBlock on the first PivotItem. I bind this to a "string" and use a button to switch between two possible contents of the button.
When the button is on the same Page/Frame it works like a charm. But when I implement a button on the MainPage and implement the same Viewmodel for the MainPage then it doesn't work. It will only change the string content on the MainPage.
Is it possible to implement the change for every Page/Frame?
And when that is done I have a Page where I gather data with a serial port.
I save the data to a List and I want to be able to use this list from 2 different Pages/Frames.
Thinking about the scenarion above then it would probably gather the data for the page where I have the button to get the data but it would probably display nothing on the other page.
How can I build it like I want it to be?
Here is a short example:
Mainpage.xaml
<StackPanel>
<Button Height="50" Width="200" Content="Change" FontSize="30" FontWeight="Bold" Margin="50 50 0 0" Click="{x:Bind MainViewModel.Change}"/>
<Pivot x:Name="MainPivot" Margin="50 50">
<PivotItem Header="Page 1">
<Frame x:Name="Page1" />
</PivotItem>
</Pivot>
</StackPanel>
Mainpage.xaml.cs
public MainPage()
{
this.InitializeComponent();
Page1.Navigate(typeof(Page1));
ViewModel = new MainViewModel();
}
public MainViewModel ViewModel { get; private set; }
Page1.xaml
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{x:Bind Path=ViewModel.StringModel.String1, Mode=TwoWay}" FontSize="50" FontWeight="Bold" />
<Button Content="Change" FontSize="30" FontWeight="Bold" Click="{x:Bind ViewModel.Change}"/>
</StackPanel>
Page1.xaml.cs
public Page1()
{
this.InitializeComponent();
ViewModel = new MainViewModel();
}
public MainViewModel ViewModel { get; private set; }
MainViewModel.cs
private StringModel _stringModel = new StringModel();
public StringModel StringModel
{
get => _stringModel;
set
{
if (_stringModel != value)
{
_stringModel = value;
OnPropertyChanged();
}
}
}
public void Change()
{
if (StringModel.String1 == "Text1")
{
StringModel.String1 = "Text2";
}
else
{
StringModel.String1 = "Text1";
}
}
StringModel.cs
private string _string1 = "XXX";
public string String1
{
get => _string1;
set
{
_string1 = value;
OnPropertyChanged();
}
}
Sounds like you are missing a "service layer" or "business layer" of your application. You need an external class which manages the data, and can provide models to populate your ViewModels:
I'd suggest using some kind of dependency injection, so each of your page view models have a reference to the DataProvider service class. This class does the serial port work to get a list of models, and provides an interface for getting data and pushing any updates to the ViewModels.
A good way of handling events that are shared, like say a "load data" button that may appear on different view models is an Event Aggregator. A service that can be injected into classes where events can be raised or subscribed to across the application.
Generally children of a XAML parent inherit the binding context of said parent.
So not sure you need to hook up a VM to your frame.
But suppose it does not work with Frames, you are creating a new MainViewModel for the frame as for the mainpage!
The solution here would be to create a singleton MainViewModel and get a hold of that one to hook up the BindingContext.
You can use Publisher-Subscriber pattern (Pub-Sub).
Already explained this here: Communication Between Views in MVVM (Pub-Sub Pattern)
ListViews follow the ItemPicker/Selector pattern of UI controls. Generally speaking, these types of controls, regardless of their platform will have a SelectedItem, and an ItemsSource. The basic idea is that there is a list of items in the ItemsSource, and the SelectedItem can be set to one of those items. Some other examples are ComboBoxes (Silverlight/UWP/WPF), and Pickers (Xamarin Forms).
In some cases, these controls are async ready, and in other cases, code needs to be written in order to handle scenarios where the ItemsSource is populated later than the SelectedItem. In our case, most of the time, the BindingContext (which contains the property bound to SelectedItem) will be set before the ItemsSource. So, we need to write code to allow this to function correctly. We have done this for ComboBoxes in Silverlight for example.
In Xamarin Forms, the ListView control is not async ready, i.e. if the ItemsSource is not populated before the SelectedItem is set, the selected item will never be highlighted on the control. This is probably by design and this is OK. The point of this thread is to find a way make the ListView async ready so that the ItemsSource can be populated after the SelectedItem is set.
There should be straight forward work arounds that can be implemented on other platforms to achieve this, but there are a few bugs in the Xamarin Forms list view that make it seemingly impossible to work around the issue. The sample app I have created is shared between WPF and Xamarin Forms in order to show how the ListView behaves differently on each platform. The WPF ListView for example, is async ready. If the ItemsSource is populated after the DataContext is set on a WPF ListView, the SelectedItem will bind to the item in the list.
In Xamarin Forms, I cannot consistently get SelectedItem two way binding on ListView to work. If I select an item in the ListView, it sets the property on my model, but if I set the property on my model, the item that should be selected is not reflected as being selected in the ListView. After the items have been loaded, when I set the property on my model, no SelectedItem is displayed. This is happening on UWP and Android. iOS remains untested.
You can see the sample problem in this Git repo:
https://ChristianFindlay#bitbucket.org/ChristianFindlay/xamarin-forms-scratch.git . Simply run the UWP, or Android sample, and click Async ListView. You can also run the XamarinFormsWPFComparison sample to see how the WPF version behaves differently.
When you run the Xamarin Forms sample, you will see that there is no item selected after the items load. However in the WPF version, it is selected. Note: It's not highlighted blue, but it is slightly grey indicating that it is selected. This is where my problem is, and the reason I can't work around the async issue.
Here is my code (clone repo for absolute latest code):
public class AsyncListViewModel : INotifyPropertyChanged
{
#region Fields
private ItemModel _ItemModel;
#endregion
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Public Properties
public ItemModel ItemModel
{
get
{
return _ItemModel;
}
set
{
_ItemModel = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ItemModel)));
}
}
#endregion
}
public class ItemModel : INotifyPropertyChanged
{
#region Fields
private int _Name;
private string _Description;
#endregion
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Public Properties
public int Name
{
get
{
return _Name;
}
set
{
_Name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
public string Description
{
get
{
return _Description;
}
set
{
_Description = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Description)));
}
}
#endregion
#region Public Methods
public override bool Equals(object obj)
{
var itemModel = obj as ItemModel;
if (itemModel == null)
{
return false;
}
var returnValue = Name.Equals(itemModel.Name);
Debug.WriteLine($"An {nameof(ItemModel)} was tested for equality. Equal: {returnValue}");
return returnValue;
}
public override int GetHashCode()
{
Debug.WriteLine($"{nameof(GetHashCode)} was called on an {nameof(ItemModel)}");
return Name;
}
#endregion
}
public class ItemModelProvider : ObservableCollection<ItemModel>
{
#region Events
public event EventHandler ItemsLoaded;
#endregion
#region Constructor
public ItemModelProvider()
{
var timer = new Timer(TimerCallback, null, 3000, 0);
}
#endregion
#region Private Methods
private void TimerCallback(object state)
{
Device.BeginInvokeOnMainThread(() =>
{
Add(new ItemModel { Name = 1, Description = "First" });
Add(new ItemModel { Name = 2, Description = "Second" });
Add(new ItemModel { Name = 3, Description = "Third" });
ItemsLoaded?.Invoke(this, new EventArgs());
});
}
#endregion
}
This is the XAML:
<Grid x:Name="TheGrid">
<Grid.Resources>
<ResourceDictionary>
<local:ItemModelProvider x:Key="items" />
</ResourceDictionary>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<ListView x:Name="TheListView" Margin="4" SelectedItem="{Binding ItemModel, Mode=TwoWay}" ItemsSource="{StaticResource items}" HorizontalOptions="Center" VerticalOptions="Center" BackgroundColor="#EEEEEE" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Label Text="{Binding Name}" TextColor="#FF0000EE" VerticalOptions="Center" />
<Label Text="{Binding Description}" Grid.Row="1" VerticalOptions="Center" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ActivityIndicator x:Name="TheActivityIndicator" IsRunning="True" IsVisible="True" Margin="100" />
<StackLayout Grid.Row="1" Orientation="Horizontal">
<Label Text="Name: " />
<Label Text="{Binding ItemModel.Name}" />
<Label Text="Description: " />
<Label Text="{Binding ItemModel.Description}" />
<Button Text="New Model" x:Name="NewModelButton" />
<Button Text="Set To 2" x:Name="SetToTwoButton" />
</StackLayout>
</Grid>
Code Behind:
public partial class AsyncListViewPage : ContentPage
{
ItemModelProvider items;
ItemModel two;
private AsyncListViewModel CurrentAsyncListViewModel => BindingContext as AsyncListViewModel;
public AsyncListViewPage()
{
InitializeComponent();
CreateNewModel();
items = (ItemModelProvider)TheGrid.Resources["items"];
items.ItemsLoaded += Items_ItemsLoaded;
NewModelButton.Clicked += NewModelButton_Clicked;
SetToTwoButton.Clicked += SetToTwoButton_Clicked;
}
private void SetToTwoButton_Clicked(object sender, System.EventArgs e)
{
if (two == null)
{
DisplayAlert("Wait for the items to load", "Wait for the items to load", "OK");
return;
}
CurrentAsyncListViewModel.ItemModel = two;
}
private void NewModelButton_Clicked(object sender, System.EventArgs e)
{
CreateNewModel();
}
private void CreateNewModel()
{
//Note: if you replace the line below with this, the behaviour works:
//BindingContext = new AsyncListViewModel { ItemModel = two };
BindingContext = new AsyncListViewModel { ItemModel = GetNewTwo() };
}
private static ItemModel GetNewTwo()
{
return new ItemModel { Name = 2, Description = "Second" };
}
private void Items_ItemsLoaded(object sender, System.EventArgs e)
{
TheActivityIndicator.IsRunning = false;
TheActivityIndicator.IsVisible = false;
two = items[1];
}
}
Note: if I change the method CreateNewModel to this:
private void CreateNewModel()
{
BindingContext = new AsyncListViewModel { ItemModel = two };
}
the SelectedItem is reflected on screen. This seems to indicate that the ListView is comparing items based on object reference as opposed to using the Equals method on the objects. I tend to think of this as a bug. But, this is not the only issue here, because if this were the only issue, then clicking the SetToTwoButton should yield the same result.
It is now clear that there are several bugs around this is Xamarin Forms. I have documented the repro steps here:
https://bugzilla.xamarin.com/show_bug.cgi?id=58451
The AdaptListView is a suitable alternative to the ListView control and isn't subject to these issues.
The Xamarin Forms team created a pull request to solve some of the issues here:
https://github.com/xamarin/Xamarin.Forms/pull/1152
But, I don't believe this pull request was ever accepted in to the master branch of Xamarin Forms.
I have several pages of an application on Windows Phone 7 using MVVM Light, when I log in the application command to call the necessary services and return me to the login page, right there by the data (username and password) to initiate session, and when I come in, in the pages following the data previously bonded user, and do not update the new data, I have the binding properties and raisePropertyChanged. How do I initialize the data again and filled again.
Thank you.
This is a example of my code in ViewModel:
public class LoginViewModel:ViewModelBase
{
ILoginService _loginService;
INavigationService _navigationService;
private string _usuario;
public string Usuario
{
get { return _usuario; }
set { if (_usuario != value) { _usuario = value; RaisePropertyChanged("Usuario"); } }
}
private string _contraseña;
public string Contraseña
{
get { return _contraseña; }
set { if (_contraseña != value) { _contraseña = value; RaisePropertyChanged("Contraseña"); } }
}
public LoginViewModel(ILoginService loginService, INavigationService navigationService)
{
//my code....
}
}
and the code XAML in my View:
<TextBlock Grid.Row="1" Text="Usuario" Style="{StaticResource TextosEtiquetasLogin}"/>
<TextBox Grid.Row="2" Text="{Binding Usuario, Mode=TwoWay}" Style="{StaticResource CuadrosTexto}" />
<TextBlock Grid.Row="3" Text="Contraseña" Style="{StaticResource TextosEtiquetasLogin}"/>
<PasswordBox x:Name="txtPass" Grid.Row="4" Password="{Binding Contraseña, Mode=TwoWay}" Style="{StaticResource CuadrosTextoPass}">
Thanks, I hope you can help me...
If you're using the SimpleIoc then it treats all resolved classes as singletons by default, including your viewmodels.
What's happening is that when you change users, SimpleIOC isn't resolving new viewmodels for your new user when they go to a page, they're re-using the viewmodel containing the data from the previous user.
You have 2 options:
1) Resolve the viewmodel so that each time you resolve it you get a new instance of the viewmodel:
SimpleIoc.Default.GetInstance<MyViewModel>(Guid.NewGuid().ToString());
2) When you change user, have the Messenger broadcast a "NewUser" event that is received by your viewmodels so that they can re-load their data based on the new user details:
In LoginViewModel:
private void LoginSuccessful()
{
Messenger.Default.Send<NewUserNotification>(new NewUserNotification("new user name"));
}
In other viewmodels:
public class MyViewModel:ViewModelBase
{
public MyViewModel()
{
Messenger.Default.Register<NewUserNotification>(ReceiveAction);
}
private void ReceiveAction(NewUserNotification user)
{
ReloadMyData(user);
}
}