I have written a Windows Store App that I need to port to Android. I am attempting to use MvvmCross and Xamarin in Visual Studio to achieve this. In my Windows App, I would create a screen using XAML and in the textbox etc. set the binding to the field in my datamodel object. I would get my datamodel objects from a WCF service reference. In the code behind for the screen, I would just set the datacontext of the root layout grid to the datamodel object generated by the Service Reference. It was pretty simple.
In MvvmCross, it seems that you basically run the viewmodel in order to load a page. The syntax for the fields in the viewmodel are really identical to the ones generated in the datamodel by the service reference. I know that Mvvm needs the viewmodel as the shim between the datamodel and the view. Is there an efficient way to pass the properties from the datamodel, through the viewmodel to the view? I have the service reference working and generating objects and data from the WCF. I could hard code each field that exists in the datamodel into the viewmodel and have the get set act on fields from the datamodel object. I was just hoping there was a less manual way to do it. Any suggestions?
#Stuart had an excellent suggestion. Here is what I did. Here is my ViewModel:
public class InventoryViewModel
: MvxViewModel
{
public async void Init(Guid ID)
{
await MPS_Mobile_Driver.Droid.DataModel.ShipmentDataSource.GetShipmentInventory(ID);
ShipmentInventory = ShipmentDataSource.CurrInventory;
Shipment = await MPS_Mobile_Driver.Droid.DataModel.ShipmentDataSource.GetShipment((int)ShipmentInventory.idno, (short)ShipmentInventory.idsub);
}
private Shipment _Shipment;
public Shipment Shipment
{
get { return _Shipment; }
set { _Shipment = value; RaisePropertyChanged(() => Shipment); }
}
private ShipmentInventory _ShipmentInventory;
public ShipmentInventory ShipmentInventory
{
get { return _ShipmentInventory; }
set { _ShipmentInventory = value; RaisePropertyChanged(() => ShipmentInventory); }
}
}
I pass it a Guid ID and in the Init method, it gets the Shipment Inventory and the Associated Shipment. When I bind the fields, I just bind to Shipment. as follows:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
style="#style/InputEditText"
local:MvxBind="Text Shipment.OrgEmail" />
</LinearLayout>
That's all there was to it!
Hope this helps someone.
Jim
Related
I am using MVVM with ReactiveUI. I have a property in the model that I want to display and be able to edit in the UI. Is there any simple way to do this using ReativeUI? The following properties should be fulfilled:
The model property implements INotifyPropertyChanged
The model property can be changed from the view model or from the model
Updates from within the model can be made on any thread
Updates from the view model should use a throttle so that not every keystroke becomes a model update
The application can be run with a UI or command line only, and the code should also be runnable in unit tests and integration tests
When using an UI, the PropertyChanged event of the ViewModel needs to be raised on the UI thread
The throttle can't be blocking in either run mode
The code should be robust, i.e. not have the risk of causing deadlocks or reverting back to old values.
I somehow imagined this would be a standard case of how to wire a view model to a model but haven't managed to get this to work, and can't really figure out any way to make it work without quite a lot of code for a seemingly simple task.
Sample code of a non-working implementation:
public interface IModel : INotifyPropertyChanged
{
string MyProperty { get; set; }
}
public class SomeViewModel : ReactiveObject
{
private readonly IModel model;
public SomeViewModel(IModel model)
{
MyProperty = String.Empty;
this.model = model;
var inputThrottleTime = TimeSpan.FromMilliseconds(500);
var scheduler = RxApp.MainThreadScheduler is AvaloniaScheduler
? RxApp.MainThreadScheduler
: RxApp.TaskpoolScheduler;
// This doesn't work. If updates are made in the model inputThrottleTime apart, the old value might be reassigned to the model.
// And also, WhenAnyValue shouldn't be used to listen on properties that might be updated on background threads according to ReactiveUI devs.
this.WhenAnyValue(x => x.model.MyProperty).ObserveOn(scheduler).Subscribe(p => MyProperty = p);
this.WhenAnyValue(x => x.MyProperty).Skip(1).Throttle(inputThrottleTime, scheduler)
.Subscribe(p => model.MyProperty = p);
}
[Reactive] public string MyProperty { get; set; }
}
You can use data binding. Chose your control component in view(xaml). After that you give a datacontext for this view. There are different ways for giving data context and item source for controls. You can do this in xaml file like this:
<Window.DataContext>
<vm:ViewModels.MainWindowViewModel />
</Window.DataContext>
<TextBlock Text="{Binding Name}"/>
After that you can access variables. This variables must have get set properties and you can use ReactiveUI in here.
public string Name
{
get => _name;
set => this.RaiseAndSetIfChanged(ref _name, value);
}
I used this in Avalonia. For more information you can look this: https://docs.avaloniaui.net/docs/data-binding
I'm wondering what is the best way to pass a file between pages in a UWP app?
I have a UWP app with two pages. In the first page, I have the user open a file with filepicker and load that file into a media player.
I want to pass that same file onto the second page when the user navigates there. I am passing the file over currently as a string which I then am attempting load as a storagefile using GetFileFromPathAsync.
This currently works as I'm able to load the file on the second page but it requires that the user provide broad file system access.
Code on Page 1 (FileLoaded is file path string):
private async void TranscodeMedia_Click(object sender, RoutedEventArgs e)
{
AppWindow appWindow = await AppWindow.TryCreateAsync();
Frame appWindowContentFrame = new Frame();
appWindowContentFrame.Navigate(typeof(TranscodeMedia), FileLoaded);
Code on Page 2:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
var fileTransfer = e.Parameter.ToString();
FileName.Text = fileTransfer;
StorageFile PassedFile = await StorageFile.GetFileFromPathAsync(fileTransfer);
I'm wondering if this is the best way to pass the file between pages? I'd rather not require the user to provide broad system access to the app if possible. Any help you can provide is most appreciated!
The best and most standard way in C#/WPF/UWP way is to use a standard pattern that consist of a general ViewModel class (which contains all the common app data that you want to use in the logic layer), put as a field in the static MainPage (or even in the App.xaml.cs class).
I always do it like this:
1) I use the MainPage automatically created as the "shell" of the app, with a property that is the AppViewModel.
The MainPage (and thus the AppViewModel) can be accessed from everywhere in the app, by setting itself as a static field in its own class (the "Current" static field can be called from everywhere in the app... even in a MessageDialog class!).
This is the code for the MainPage (or a shell Page that you wish, but I suggest doing like this, it is a pretty standard way used even by Microsoft), simpler than you think:
public sealed partial class MainPage : Page
{
public AppViewModel ViewModel { get; set; } = new AppViewModel();
public static MainPage Current { get; set; }
public MainPage()
{
this.InitializeComponent();
Current = this;
}
}
THIS is the trick: to make the page static in one field in its
own class, so that that static field will be UNIQUE in the entire app
(this is one of the main features of the "static" word) and, thus, by calling
MainPage.Current.ViewModel you can immediately get any data (in your
specific case, a StorageFile) stored there.
2) The AppViewModel itself is a class that must implement the INotifyPropertyChanged interface, in order to enable bindable properties and functions.
It is common, among Windows developers, to create a base class that implements it and then derive all the classes that needs bindable (i.e. observable) properties from it.
Here it is, exactly how Microsoft itself creates it:
public class BaseBind : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected bool SetProperty<T>(ref T storage, T value,
[CallerMemberName] String propertyName = null)
{
if (object.Equals(storage, value)) return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
}
Then you derive AppViewModel class (and all the other model and viewmodel classes) from it… populating it with all the common properties that you need to share across pages.
I have even added a derived property, in order to show how you can share even multiple data types at once, and a function:
public class AppViewModel : BaseBind
{
public AppViewModel()
{
// Usually we initialize all the starting data here, in the viewmodel constructor...
}
// All common app data
private string sampleCommonString;
public String SampleCommonString
{
get { return sampleCommonString; }
set { SetProperty(ref sampleCommonString, value); OnPropertyChanged(nameof(SampleDerivedProperty1)); OnPropertyChanged(nameof(SampleDerivedProperty2)); }
}
public String SampleDerivedProperty1 => "return something based on SampleCommonString";
public String SampleDerivedProperty2
{
get
{
// evaluate in some way SampleCommonString...
return "Same thing as SampleDerivedProperty1, but it allows to add more than just one istruction";
}
}
// This is a property that you can use for functions and internal logic… but it CAN'T be binded to the UI directly
public String SampleNOTBindableProperty { get; set; }
public void SampleFunction()
{
// Insert code, that needs to interact with all the data contained in the viewmodel itself, here...
// The function has to be with NO parameters, in order to work with simple {x:Bind} markup.
// If your function has to access some specific data, you can create a new bindable (or non) property, just as the ones above, and memorize the data there.
}
}
3) Then, in order to access all this from another Page, just create an AppViewModel field in that page, referencing the viewmodel contained in the static mainpage:
public sealed partial class SecondPage : Page
{
public AppViewModel ViewModel => MainPage.Current.ViewModel;
public SecondPage()
{
this.InitializeComponent();
}
}
...and you can easily bind XAML controls properties to the AppViewModel itself:
<TextBlock Text="{x:Bind ViewModel.SampleCommonString, Mode=OneWay}"/>
<TextBox Text="{x:Bind ViewModel.SampleCommonString, Mode=TwoWay}"/>
<Button Content="Sample content" Click="{x:Bind ViewModel.SampleFunction}"/>
(Mode=OneWay is for real-time binding, in order that the property is immediately updated even in the UI, while Mode=TwoWay is used for those properties that can be edited from the control itself, by the user, in order to interact with app logic).
In this mode you will be able to display data and all its changes in real-time!
So... this is the way to keep all the app data at run-time in a
correct and flexible way... by learning it and practicing, in the
future you will use this pattern even in a smarter way, by creating
viewmodels for every object of your application (for example: if
your app need to store your company's customers data, you will have a
"CustomerViewModel" class derived from the BaseBind class, with all
the data of a customer in it) and creating lists like
ObservableCollection<SampleViewModel> to store all of them (ObservableCollection<t> is a collection type that has built-in mechanism to handle list changes, like adding, removing and reordering list items).
Then you will link every observable collection to the ItemsSource property of a control that inherits from ListBase class (tipically: ListView or GridView), creating a DataTemplate to display each list item, like in this example:
<Page
xmlns:vm="using:SampleApp.ViewModelsPath"
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.SampleListOfObjectViewModel, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="vm:SampleObjectViewModel">
<StackPanel>
<TextBlock Text="{x:Bind SampleObjectProperty1, Mode=OneWay}"/>
<TextBlock Text="{x:Bind SampleObjectProperty2, Mode=OneWay}"/>
<Button Click="{x:Bind SampleObjectFunction}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
...and all the data displayed will be updated in real-time whenever you change it!
Hope this all will help you boost your knowledge about how preparing a WPF/UWP logic layer, because all of this works pretty in the same way even for the WPF apps (i.e. the old desktop programs).
Best regards
There are some other ways to implement your requirement about accessing the same file on different pages. But for your scenario, you could use Future-access list in your UWP app.
By picking files and folders, your user grants your app permission to access items that might not be accessible otherwise. If you add these items to your future-access list then you'll retain that permission when your app wants to access those items again later.
Here is the sample code I made
In the first page:
FileOpenPicker picker = new FileOpenPicker();
picker.FileTypeFilter.Add("*");
StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
{
// add file to the Future Access list
var storageItemAccessList = Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList;
// this token is the key to get the file.
string FALToken = storageItemAccessList.Add(file, "mediaFile");
// in your real scenario, you need to save the token and pass it when you nee
this.Frame.Navigate(typeof(TestPage), FALToken);
}
In the second page:
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
string token = (string)e.Parameter;
var storageItemAccessList = StorageApplicationPermissions.FutureAccessList;
StorageFile retrievedFile = await storageItemAccessList.GetFileAsync(token);
}
So you don't need the broad file system access if you use Future-access list to keep the permission of files.
For more detailed information, please refer to this document: Track recently used files and folders
I'm trying to bind to a Spinner using ReactiveUI in a Xamarin.Android application. To add items to the Spinner, I need to use ArrayAdapter. But ArrayAdapter needs Android.Content.Context. Should I pass it into the ViewModel?
Anyone know about an application written in Xamarin.Android, which uses ReactiveUI, where I could look inspiration? The ReactiveUI documentation only has a reference to a sample application written for iOS.
View
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="#+id/Label"
android:text="Zařízení:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Spinner
android:id="#+id/Devices"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#id/Label"/>
<Button
android:id="#+id/EditCommand"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="#+id/Devices"
android:text="Edit"/>
</RelativeLayout>
Activity
namespace Test.Droid
{
[Activity(Label = "Test.Droid", MainLauncher = true)]
public class MainActivity : ReactiveActivity, IViewFor<MainViewModel>
{
public Spinner Devices { get; private set; }
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.Main);
ViewModel = new MainViewModel();
this.WireUpControls();
// Bindings
this.Bind(this.ViewModel, ) // ?
}
private MainViewModel _viewModel;
public MainViewModel ViewModel
{ get => _viewModel; set { this.RaiseAndSetIfChanged(ref _viewModel, value); } }
object IViewFor.ViewModel
{ get => ViewModel; set { ViewModel = (MainViewModel)value; } }
}
}
ViewModel
namespace Test.Droid.ViewModels
{
public class MainViewModel : ReactiveObject
{
// How to databind Spinner Devices ?
public MainViewModel()
{
}
}
}
I haven't done any Xamarin.Android development, but in general you don't want to pass details about the view into the ViewModel - it should not know anything about the view.
I would expose the list of items as a collection (e.g. IList<Item>) and use a converter on the binding to create an ArrayAdapter:
this.OneWayBind(this.ViewModel.Devices, this.View.Property, devices => new ArrayAdapter(devices));
this.View.Property should refer to the property that changes whenever the list of devices changes. The third parameter (devices => new ArrayAdapter()) receives the property from the ViewModel as an argument, you then return a value that can be set on the this.View.Property.
For example:
ViewModel.Count is a string
View.Property is an int
Bind like this:
this.OneWayBind(this.ViewModel.Count, this.View.Property, count => int.Parse(count));
The third parameter can be a function or lambda that accepts an argument of the type of the ViewModel property and returns a value of the type of the view property.
I'd said I'd given in providing any feedback on Stackoverflow but anyway... we have a collection of binding extensions, one of which is for a Spinner. in typical usage it looks like this
// bind the options for the distance
this.BindSpinner(ViewModel,
vm => vm.TravelLimitSelected,
vm => vm.CurrentTravelLimit,
vm => vm.TravelLimitChoices.Distances,
f => f.travelLimitSpinner,
(items) =>
new ActionSpinnerAdapter<DistanceChoiceModel>((c, p) => new DistanceLimitViewHost(c, p), items));
where in this case, the extension method looks like
public static IDisposable BindSpinner<TView, TViewModel, TCommandProp, TSpinnerViewModel>(
this TView view,
TViewModel viewModel,
Expression<Func<TViewModel, TCommandProp>> viewModelCommandName,
Expression<Func<TViewModel, int>> viewModelSelectedPropertyName,
Expression<Func<TViewModel, IList<TSpinnerViewModel>>> viewModelSourceItemsName,
Expression<Func<TView, Spinner>> spinnerControl, Func<IList<TSpinnerViewModel>, ISpinnerAdapter> adapterFactory) where TViewModel : RxViewModel
where TCommandProp : ICommand
where TView : class, IViewFor<TViewModel>
where TSpinnerViewModel : class
{
I have put the Bootstrapper in a separate assembly than ViewModels and Views.
However the DesignTime support seems to work for simple *ViewModel->*View visualizations when there is no other Screen embedded that needs to resolve the corresponding view. But I can't get it running for child ViewModels. I get an Exception instead...
Container with Child ViewModel:
<UserControl ...
d:DataContext="{d:DesignInstance Type=vm:DesignSomeContainerViewModel, IsDesignTimeCreatable=True}"
cal:Bind.AtDesignTime="True ... >
<ContentControl cal:View.Model={Binding PropertyWithChildModel} />
// where PropertyWithChildModel is another Screen
The Screen within the property is working correctly when displaying on it's own at DesignTime but not inside the ContentControl of another View... then the DesignTime support gets broken. I followed the guidance as suggested here Caliburn Micro Documentation. But there is nothing said about specialties when using nested resolving.
My project structure:
[Application.exe]
- MyNameSpace.ApplicationName
- Bootstrapper (knows assemblies with views)
[Satellite.dll]
- MyNameSpace.SomeWhere
- SomeContainerViewModel
- [DesignTime]/DesignSomeContainerViewModel (Folder is Namespaceprovider)
- SomeContainerView (SomeContainerView is set to look at DesignSomeContainerViewModel at DesignTime)
- [MyChilds]/SomeChildViewModel
- [DesignTime]/DesignSomeChildViewModel
- [MyChilds]/SomeChildView (SomeChildView is set to look at DesignSomeChildViewModel)
Class model:
public interface ISomeChildViewModel
{
string SomeValue {get;}
}
// should not be used during design time
public class SomeChildViewModel : Screen, ISomeChildViewModel
{
// real implementation
}
// this is used at design time
public class DesignSomeChildViewModel : Screen, ISomeChildViewModel
{
public DesignSomeChildViewModel()
{
this.SomeValue = "Hello World!";
// showing up correctly in the Xaml View at DesignTime
}
string SomeValue {get;set;}
}
public interface ISomeContainerViewModel
{
IScreen PropertyWithChildModel{get;}
}
// should not be used during design time
public class SomeContainerViewModel : Screen, ISomeContainerViewModel
{
// real implementation
}
// used during design time
public class DesignSomeContainerViewModel : Screen, ISomeContainerViewModel
{
public DesignSomeContainerViewModel ()
{
// this assignment leads to an exception that the IoC is not ready
// "IoC is not initialized."
this.PropertyWithChildModel = new DesignSomeChildViewModel();
}
IScreen PropertyWithChildModel
{
get { return propertyWithChildModel; }
set
{
this.propertyWithChildModel=value;
this.NotifyOfPropertyChange();
}
}
}
What I already tried/checked:
Bootstrapper: list of assemblies from SelectAssembly contains assembly with Views
ViewModelLocator: was set to match against DesignTime ViewModel instead of ViewModel (even if not, there should be a message that the View can't be resolved for the ViewModel)
used Conductor with ActiveItem instead of simple property
the container DesignSomeContainerViewModel and SomeContainerViewModel share same interface with IScreen PropertyWithChild {get;} and binding points to the right property
I have a list of items bound to a MvxBindableListView with a MvxItemTemplate.
I usually have 4 items in my list bound to my view. Data gets updated and the view displays the new data just fine.
Now, I want to add two buttons to this item template. However, relative source binding is not available with MvvmCross. (see image)
But I'm having difficulties working out a solution to this.
I have tried the ItemClick binding of the list item, but that only gives me 1 possibility of click and I need 2.
Can anyone help?
See the second option in the answer in MVVMCross changing ViewModel within a MvxBindableListView - this covers one way to do this.
Using that approach you'd expose a list of objects like:
public class Wrapped
{
public ICommand GoThruCommand { get; set; }
public ICommand OpenCommand { get; set; }
public string Name { get; set; }
}
and you'd use an axml list template with bound controls like:
<TextView
...
local:MvxBind="{'Text':{'Path':'Name'}}" />
<Button
...
local:MvxBind="{'Click':{'Path':'GoCommand'}}" />
<Button
...
local:MvxBind="{'Click':{'Path':'ThruCommand'}}" />
if you've got suggestions/requests for relative source in mvx, please add them to https://github.com/slodge/MvvmCross/issues/35