Markup extension in XAML for binding to ISubject<string> - c#

If I have the following view model
class Foo : INotifyPropertyChanged {
ISubject<string> Name { ... }
}
and some imagined XAML code
<TextBox Text="{my:Subscribe Path=Name}/>
I wish the two way binding to behave that
Subject.onNext is called when the text box is updated in the UI
the text box is updated by subscribing to the Subject.Subscribe
As WPF only supports INPC directly my idea is to create a proxy INPC object
in via a markup extension
class WPFSubjectProxy : INotifyPropertyChanged{
string Value { ... }
}
The proxy would be wired up to the subject as so
subject.Subscribe(v=>proxy.Value=v);
proxy
.WhenAny(p=>p.Value, p.Value)
.Subscribe(v=>subject.OnNext(v))
Note WhenAny is a ReactiveUI helper for subscribing to
INPC events.
But then I would need to generate a binding and return
that via the markup extension.
I know what I want to do but can't figure out the
Markup extension magic to put it all together.

It's hard to say without seeing specifically what you're struggling with, but perhaps this helps?
EDIT
The solution I (bradgonesurfing) came up with is below thanks to the pointer in the
assigned correct answer.
    Nodes
     
and the implementing code. It has a dependency on ReactiveUI and a helper function in a private library for binding ISubject to a mutable property on an INPC supporting object
using ReactiveUI.Subjects;
using System;
using System.Linq;
using System.Reactive.Subjects;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace ReactiveUI.Markup
{
[MarkupExtensionReturnType(typeof(BindingExpression))]
public class SubscriptionExtension : MarkupExtension
{
[ConstructorArgument("path")]
public PropertyPath Path { get; set; }
public SubscriptionExtension() { }
public SubscriptionExtension(PropertyPath path)
{
Path = path;
}
class Proxy : ReactiveObject
{
string _Value;
public string Value
{
get { return _Value; }
set { this.RaiseAndSetIfChanged(value); }
}
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var pvt = serviceProvider as IProvideValueTarget;
if (pvt == null)
{
return null;
}
var frameworkElement = pvt.TargetObject as FrameworkElement;
if (frameworkElement == null)
{
return this;
}
object propValue = GetProperty(frameworkElement.DataContext, Path.Path);
var subject = propValue as ISubject<string>;
var proxy = new Proxy();
Binding binding = new Binding()
{
Source = proxy,
Path = new System.Windows.PropertyPath("Value")
};
// Bind the subject to the property via a helper ( in private library )
var subscription = subject.ToMutableProperty(proxy, x => x.Value);
// Make sure we don't leak subscriptions
frameworkElement.Unloaded += (e,v) => subscription.Dispose();
return binding.ProvideValue(serviceProvider);
}
private static object GetProperty(object context, string propPath)
{
object propValue = propPath
.Split('.')
.Aggregate(context, (value, name)
=> value.GetType()
.GetProperty(name)
.GetValue(value, null));
return propValue;
}
}
}

Related

Resolve ViewModels in separated assembly with ViewModelLocator in Prism 6

I am trying to connect the DataContexts of my views to view models from another separated assembly.
Brian Lagunas wrote on his blog something to Getting Started with Prism’s new ViewModelLocator, However, his solution is specially to customize the conventions to allow the ViewModelLocator resolving the view models types.
My scenario:
I have the main project (MyApplication.exe) contains the Bootstrapper, Shell and the views
In another separated assembly (MyApplication.Process.dll) i have all the view models.
Basing on Brian's explication, i tried the following solution:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
viewType =>
{
var viewName = viewType.FullName;
var viewAssemblyName = viewType.Assembly.GetName().Name;
var viewModelNameSuffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
var viewModelName = viewName.Replace("Views", "ViewModels") + viewModelNameSuffix;
viewModelName = viewModelName.Replace(viewAssemblyName, viewAssemblyName + ".Process");
var viewModelAssemblyName = viewAssemblyName + ".Process";
var viewModelTypeName = string.Format(
CultureInfo.InvariantCulture,
"{0}, {1}",
viewModelName,
viewModelAssemblyName);
return Type.GetType(viewModelTypeName);
});
}
The solution above works correctly, However, i don't know if this is the best way to do that?
All what i want, is to tell Prism ViewModelLocator in which Assemblies it has to find the view models, i mean the same approach of Caliburn.Micro (Looks for the view models in all registered assemblies).
The solution above will not work if my application support the Prism Modularity if the assembly name doesn't end with the word 'Process' for example?
What do you suggest for me ?
Your code of resolving the viewmodel type is certainly ok. If you look to the codebase of Prism, you'll notice a quite similar way using minor reflection and string replacements.
static Func<Type, Type> _defaultViewTypeToViewModelTypeResolver =
viewType =>
{
var viewName = viewType.FullName;
viewName = viewName.Replace(".Views.", ".ViewModels.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var suffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
var viewModelName = String.Format(CultureInfo.InvariantCulture, "{0}{1}, {2}", viewName, suffix, viewAssemblyName);
return Type.GetType(viewModelName);
};
The thing with convention based type resolution is that you have to go with the default convention or create your own convention and stick to the chosen convention. Meaning that if in your first module you choose to have the viewmodels in a .Process assembly, that you should do this for all your other modules. Sticking to your convention is the easiest way to go.
The good news is: if you don't like convention based resolution, you can override the resolution like you already did and implement any kind of resolution you like, how complex you want it to be. Nothing keeps you from e.g. keeping a dictionary mapping views to viewmodels (which we actually did for one project). Filling up this dictionary (or setting up another way of resolution) would be done in the ModuleCatalog for each module.
I finally solved my issue by setting custom view model resolver to search for view models in all added assemblies catalogs.
Solution
First of all, i try to apply the default prism view model locator convention, if no viewmodel found then, i start applying my custom one.
1- I start by getting all assemblies from the AggregateCatalog.
2- I Get all the non-abstract exported types inherit from Prism BindableBase.
3- I Apply the custom convention delegate to get the expected view model.
In my case, the custom convention is all types having the suffix "ViewModel" and the prefix is the view type name:
Example:
If the view name is "UsersView" the view model should be "UsersViewModel".
If the view name is "Users" the view model should be also "UsersViewModel".
Code:
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
viewType =>
{
// The default prism view model type resolver as Priority
Type viewModelType = this.GetDefaultViewModelTypeFromViewType(viewType);
if (viewModelType != null)
{
return viewModelType;
}
// IF no view model found by the default prism view model resolver
// Get assembly catalogs
var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);
// Get all exported types inherit from BindableBase prism class
var bindableBases =
assemblyCatalogs.Select(
c =>
((AssemblyCatalog)c).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(BindableBase)))
.Select(t => t)).SelectMany(b =>
{
var types = b as IList<Type> ?? b.ToList();
return types;
}).Distinct() ;
// Get the type where the delegate is applied
var customConvention = new Func<Type, bool>(
(Type t) =>
{
const string ViewModelSuffix = "ViewModel";
var isTypeWithViewModelSuffix = t.Name.EndsWith(ViewModelSuffix);
return (isTypeWithViewModelSuffix)
&& ((viewType.Name.EndsWith("View") && viewType.Name + "Model" == t.Name)
|| (viewType.Name + "ViewModel" == t.Name));
});
var resolvedViewModelType = bindableBases.FirstOrDefault(customConvention);
return resolvedViewModelType;
});
The method * GetDefaultViewModelTypeFromViewType * Is the default prism view model locator, its code is exactly the same as in Bart's answer.
I hope this will be helpful for others.
Edit:
I finally solved the problem by creating a new custom MvvmTypeLocator:
public interface IMvvmTypeLocator
{
#region Public Methods and Operators
Type GetViewModelTypeFromViewType(Type viewType);
Type GetViewTypeFromViewModelType(Type viewModelType);
Type GetViewTypeFromViewName(string viewName);
#endregion
}
The implementation :
public class MvvmTypeLocator: IMvvmTypeLocator
{
private AggregateCatalog AggregateCatalog { get; set; }
public MvvmTypeLocator(AggregateCatalog aggregateCatalog)
{
this.AggregateCatalog = aggregateCatalog;
}
public Type GetViewModelTypeFromViewType(Type sourceType)
{
// The default prism view model type resolver as Priority
Type targetType = this.GetDefaultViewModelTypeFromViewType(sourceType);
if (targetType != null)
{
return targetType;
}
// Get assembly catalogs
var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);
// Get all exported types inherit from BindableBase prism class
var bindableBases =
assemblyCatalogs.Select(
c =>
((AssemblyCatalog)c).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(BindableBase)))
.Select(t => t)).SelectMany(b =>
{
var types = b as IList<Type> ?? b.ToList();
return types;
}).Distinct();
// Get the type where the delegate is applied
var customConvention = new Func<Type, bool>(
(Type t) =>
{
const string TargetTypeSuffix = "ViewModel";
var isTypeWithTargetTypeSuffix = t.Name.EndsWith(TargetTypeSuffix);
return (isTypeWithTargetTypeSuffix)
&& ((sourceType.Name.EndsWith("View") && sourceType.Name + "Model" == t.Name)
|| (sourceType.Name + "ViewModel" == t.Name));
});
var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
return resolvedTargetType;
}
public Type GetViewTypeFromViewModelType(Type sourceType)
{
// Get assembly catalogs
var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);
// Get all exported types inherit from BindableBase prism class
var bindableBases =
assemblyCatalogs.Select(
c =>
((AssemblyCatalog)c).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(IView)))
.Select(t => t)).SelectMany(b =>
{
var types = b as IList<Type> ?? b.ToList();
return types;
}).Distinct();
// Get the type where the delegate is applied
var customConvention = new Func<Type, bool>(
(Type t) =>
{
const string SourceTypeSuffix = "ViewModel";
var isTypeWithSourceTypeSuffix = t.Name.EndsWith(SourceTypeSuffix);
return (isTypeWithSourceTypeSuffix)
&& ((sourceType.Name.EndsWith("View") && t.Name + "Model" == sourceType.Name)
|| (t.Name + "ViewModel" == sourceType.Name));
});
var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
return resolvedTargetType;
}
public Type GetViewTypeFromViewName(string viewName)
{
// Get assembly catalogs
var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);
// Get all exported types inherit from BindableBase prism class
var bindableBases =
assemblyCatalogs.Select(
c =>
((AssemblyCatalog)c).Assembly.GetExportedTypes()
.Where(t => !t.IsAbstract && typeof(IView).IsAssignableFrom(t) && t.Name.StartsWith(viewName))
.Select(t => t)).SelectMany(b =>
{
var types = b as IList<Type> ?? b.ToList();
return types;
}).Distinct();
// Get the type where the delegate is applied
var customConvention = new Func<Type, bool>(
(Type t) =>
{
return t.Name.EndsWith("View");
});
var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
return resolvedTargetType;
}
private Type GetDefaultViewModelTypeFromViewType(Type viewType)
{
var viewName = viewType.FullName;
viewName = viewName.Replace(".Views.", ".ViewModels.");
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var suffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
var viewModelName = String.Format(
CultureInfo.InvariantCulture,
"{0}{1}, {2}",
viewName,
suffix,
viewAssemblyName);
return Type.GetType(viewModelName);
}
}
This custom type locator is using the AggregateCatalog to search the target types in all the assembly catalogs. Of course i create its instance on the Bootstrapper once the Bootstrapper's AggregateCatalog is configured:
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(LoginViewModel).Assembly));
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(LoginView).Assembly));
this.mvvmTypeLocator = new MvvmTypeLocator(this.AggregateCatalog);
}
At the end, i just configure the view model locator in the Bootstrapper like the following:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
viewType => this.mvvmTypeLocator.GetViewModelTypeFromViewType(viewType));
}
Note that the methods GetViewTypeFromViewModelType and GetViewTypeFromViewName are searching all the views implementing the interface named IView. this just an empty interface i use to distinct my views from other classes inside the same assembly. If someone use this mvvmTypeLocator, then he has to create his own interface and implement all the views that should be discoverable by the mvvmTypeLocator.
What about this workaround ? -I really gave up on editing the ViewModelLocator- make a ViewModule within the same project and let it inherit from another ViewModel in the other assembly, the core implementation is in the base ViewModel and you can still bind to it and do everything you want.
I marked all the functions in the Base class as virtual so I can extend their functionality if I want to use some platform specific components ex. IRegionManager
this is the code from the platform project ( WPF )
namespace PrismApplicationTest0.ViewModels
{
public class ViewAViewModel : ViewAViewModelBase
{
private readonly IRegionManager _regionManager;
public ViewAViewModel(IEventAggregator eventAggregator,IRegionManager regionManager) : base(eventAggregator)
{
_regionManager = regionManager;
}
protected override void UpdateMethod()
{
// After completing the core functionality
base.UpdateMethod();
// Switch to another page using platform specific region manager
_regionManager.RequestNavigate(RegionNames.ContentRegion,"ViewB");
}
}
}
this is the code from the PCL ( portable class library )
using System;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;
namespace MainModule.ViewModels
{
public abstract class ViewAViewModelBase : BindableBase
{
private readonly IEventAggregator _eventAggregator;
private string _firstName;
private string _lastName;
private DateTime? _lastUpdated;
public string FirstName
{
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
public string LastName
{
get { return _lastName; }
set { SetProperty(ref _lastName, value); }
}
public DateTime? LastUpdated
{
get { return _lastUpdated; }
set { SetProperty(ref _lastUpdated, value); }
}
public DelegateCommand UpdateCommand { get; private set; }
public ViewAViewModelBase(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
UpdateCommand =
new DelegateCommand(UpdateMethod, CanUpdateMethod)
.ObservesProperty(() => FirstName)
.ObservesProperty(() => LastName);
}
protected bool CanUpdateMethod()
{
return !String.IsNullOrEmpty(_lastName) && !String.IsNullOrEmpty(_firstName);
}
protected virtual void UpdateMethod()
{
LastUpdated = DateTime.Now;
_eventAggregator.GetEvent<Events.UpdatedAggEvent>().Publish($"User {FirstName}");
}
}
}
it's working as a charm with me.
I guess if you need another object from other assemblies you can create instances of them in the base class
Good luck

MVVM adding a list from a static service into the view?

I have created a service (static methods), which will get for instance all folders within google drive and returning a List<File>. (those methods are async MSDN Async Programming)
The problem is I dont know how to pass my results into the view. I tried to using a ObservableCollection but I cant make it work.
And one more thing is that Im not sure if its in my usage useful.
I dont add one item or delete one item. I just scrap the whole folders every refresh. What I have understood is that this is useful for a collection of data which will edited by the user.
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
var service = DriveHelper.createDriveService("client_secret.json", false)
// ERROR
_googleDriveFolders = new NotifyTaskCompletion<List<File>>( DriveHelper.getFiles(service), "trashed=false and mimeType = 'application/vnd.google-apps.folder'"));
}
public NotifyTaskCompletion<List<File>> googleDriveFolders { get; private set; }
private ObservableCollection<File> _googleDriveFolders;
public ObservableCollection<File> googleDriveFolder
{
get { return _googleDriveFolders; }
set
{
_googleDriveFolders = value;
RaisePropertyChanged();
}
}
//...
As noted in the comments, the issue is that your NotifyTaskCompletion immediately returns and gets assigned to the refreshFoldersCommand (btw. in C# naming conventions properties are in Pascal Case aka Camel Uppercase, not Camel lower case notation) property and the event is raised up immediately and not after the async operation finishes.
It's very bad practice to put async code into the ViewModels constructor (or any constructor for that case), because inside a constructor you can't await the async method.
There is no easy solution for it. The correct solution requires you to change your applications architecture and make use of a navigation service. I've posted it a few times already here on StackOverflow.
Prism (Microsoft's MVVM Framework) does come with a clean solution. It has an INavigationAware interface, that contains 3 methods (OnNagivatedTo, OnNavigatedFrom and IsNavigatioNTarget). To async load data into your ViewModel, NavigateTo is the important one.
In Prism it is called, after the previous View was unloaded (after calling NavigateFrom in the former ViewModels class) and the newly one has been instantiated and assigned to the new View. Parameters passed to theNavigationService.Navigate(..)method are passed toOnNagivatedTo` method of the ViewModel.
It can be marked as async and you can put your code there and await it
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
}
public NotifyTaskCompletion<List<File>> googleDriveFolders { get; private set; }
private ObservableCollection<File> _googleDriveFolders;
public ObservableCollection<File> googleDriveFolder
{
get { return _googleDriveFolders; }
set
{
_googleDriveFolders = value;
RaisePropertyChanged();
}
}
public async void OnNavigatedTo(NavigationContext context)
{
var service = DriveHelper.createDriveService("client_secret.json", false)
// ERROR
googleDriveFolder = await DriveHelper.getFiles(service), "trashed=false and mimeType = 'application/vnd.google-apps.folder'");
}
...
}
Edit:
Further answers about the same issue:
Pass parameter to a constructor in the ViewModel
How can I open another view in WPF MVVM using click handlers and commands? (Is my solution reasonable?)
Edit 2:
Also, you are assigning your NotifyTaskCompletion to _googleDriveFolders which is your backing field for the googleDriveFolder property, hence the RaisePropertyChanged(); is never called.
**Edit 3: **
As of your code from that tutorial, your code isn't exactly following the tutorial. The guy in the tutorial is binding to the property NotifyTaskCompletion. You are binding it to the backing field though.
public MainWindowViewModel()
{
var service = DriveHelper.createDriveService("client_secret.json", false)
// your property is named googleDriveFolders, but you are assigning it to _googleDriveFolders
googleDriveFolders = new NotifyTaskCompletion<List<File>>( DriveHelper.getFiles(service), "trashed=false and mimeType = 'application/vnd.google-apps.folder'"));
}
This code, when complete, won't call RaisePropertyChanged("googleDriveFolder") (which is your observable list), because NotifyTaskCompletion will only refresh it's own property. It's very likely you have bounded your View to the googleDriveFolder (Observable property) rather than to googleDriveFolders.Result.
For this example it's imperative to bind to googleDriveFolders.Result, because the change notification will only get fired for the Result Property of the NotificationTaskCompletition object as seen in the examples code propertyChanged(this, new PropertyChangedEventArgs("Result"));.
So your XAML has to look something like
<ListView Source="{Binding googleDriveFolders.Result}"/>
But anyhows, the issue still remains, that it's bad practice to do async operations within the constructor, so even within your Unit Tests for example, it would start the async task everytime the object is initialized, so in every UnitTest even if you test different stuff and you can't pass parameters to it easily (like passing a link or a folder name which to load).
So the clean way is doing it via navigation service and an INavigationAware implementation for ViewModels that require it (modes that do not do any async operation just don't implement this interface).
I came to this solution ... But I think this isn't the best way. <.<
Using a Listview.
namespace UpdateUploader.ViewModels
{
using System.Windows.Input;
using Helper;
using Services;
using System.Collections;
using System.Collections.ObjectModel;
using Google.Apis.Drive.v2.Data;
using Google.Apis.Drive.v2;
using System.Collections.Generic;
public class MainWindowViewModel : ViewModelBase
{
DriveService _service;
public MainWindowViewModel()
{
_service = DriveHelper.createDriveService("client_secret.json", false);
googleDriveFolders = new NotifyTaskCompletion<List<File>>( DriveHelper.getFiles(_service, "trashed=false and mimeType = 'application/vnd.google-apps.folder'"));
}
public NotifyTaskCompletion<List<File>> _googleDriveFolders;
public NotifyTaskCompletion<List<File>> googleDriveFolders
{
get { return _googleDriveFolders; }
set
{
_googleDriveFolders = value;
RaisePropertyChanged();
}
}
#region ICommands
private ICommand _refreshFoldersCommand;
public ICommand refreshFoldersCommand
{
get
{
if (this._refreshFoldersCommand == null)
{
_refreshFoldersCommand = new RelayCommand(p => this.loadFolders(p));
}
return this._refreshFoldersCommand;
}
}
#endregion ICommands
public void loadFolders(object parameter)
{
googleDriveFolders = new NotifyTaskCompletion<List<File>>(DriveHelper.getFiles(_service, "trashed=false and mimeType = 'application/vnd.google-apps.folder'"));
}
}
}
EDIT
NotifyTaskCompletion.cs
namespace UpdateUploader.Helper
{
using System;
using System.ComponentModel;
using System.Threading.Tasks;
public sealed class NotifyTaskCompletion<TResult> : INotifyPropertyChanged
{
public NotifyTaskCompletion(System.Threading.Tasks.Task<TResult> task)
{
Task = task;
if (!task.IsCompleted)
{
var _ = WatchTaskAsync(task);
}
}
private async Task WatchTaskAsync(Task task)
{
try
{
await task;
}
catch
{
}
var propertyChanged = PropertyChanged;
if (propertyChanged == null)
return;
propertyChanged(this, new PropertyChangedEventArgs("Status"));
propertyChanged(this, new PropertyChangedEventArgs("IsCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("IsNotCompleted"));
if (task.IsCanceled)
{
propertyChanged(this, new PropertyChangedEventArgs("IsCanceled"));
}
else if (task.IsFaulted)
{
propertyChanged(this, new PropertyChangedEventArgs("IsFaulted"));
propertyChanged(this, new PropertyChangedEventArgs("Exception"));
propertyChanged(this, new PropertyChangedEventArgs("InnerException"));
propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage"));
}
else
{
propertyChanged(this, new PropertyChangedEventArgs("IsSuccessfullyCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("Result"));
}
}
public Task<TResult> Task { get; private set; }
public TResult Result
{
get { return (Task.Status == TaskStatus.RanToCompletion) ? Task.Result : default(TResult); }
}
public TaskStatus Status { get { return Task.Status; } }
public bool IsCompleted { get { return Task.IsCompleted; } }
public bool IsNotCompleted { get { return !Task.IsCompleted; } }
public bool IsSuccessfullyCompleted
{
get
{
return Task.Status == TaskStatus.RanToCompletion;
}
}
public bool IsCanceled { get { return Task.IsCanceled; } }
public bool IsFaulted { get { return Task.IsFaulted; } }
public AggregateException Exception { get { return Task.Exception; } }
public Exception InnerException
{
get
{
return (Exception == null) ? null : Exception.InnerException;
}
}
public string ErrorMessage
{
get
{
return (InnerException == null) ? null : InnerException.Message;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
DriveService.cs(excerpt; createDriveService, getfiles)
namespace UpdateUploader.Services
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v2;
using Google.Apis.Drive.v2.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;
using Google.Apis.Upload;
class DriveHelper
{
private static bool _unique;
public static DriveService createDriveService(string passFilePath, bool createUniqueID)
{
_unique = createUniqueID;
if (!System.IO.File.Exists(passFilePath))
{
Console.Error.WriteLine("keyfile not found...");
return null;
}
string[] scopes = new string[] { DriveService.Scope.Drive }; // Full accces
// loading the key file
UserCredential credential;
using (var stream = new System.IO.FileStream("client_secret.json", System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
string credPath = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal);
credPath = System.IO.Path.Combine(credPath, ".credentials/update-uploader");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
Console.WriteLine("Credential file saved to: " + credPath);
}
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Update Uploader",
});
return service;
}
// search = null ; get all files/folders
public static async Task<List<File>> getFiles(DriveService service, string search)
{
System.Collections.Generic.List<File> Files = new System.Collections.Generic.List<File>();
try
{
// list all files with max 1000 results
FilesResource.ListRequest list = service.Files.List();
list.MaxResults = 1000;
if (search != null)
{
list.Q = search;
}
FileList filesFeed = await list.ExecuteAsync();
while (filesFeed.Items != null)
{
foreach (File item in filesFeed.Items)
{
Files.Add(item);
}
// if it is the last page break
if (filesFeed.NextPageToken == null)
{
break;
}
list.PageToken = filesFeed.NextPageToken;
filesFeed = list.Execute();
}
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.Message);
}
return Files;
}
ViewModelBase.cs
namespace UpdateUploader.Helper
{
using System.ComponentModel;
using System.Runtime.CompilerServices;
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
// new since 4.6 or 4.5
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Can you make a method part of the default setter action in a property variable?

If you have multiple properties that implement the same method in the setter, is there a way to make it part of the default setter?
If I have multiple properties that call a Filter() when they are set, is there a way to push it into a "base setter" so that I don't have to have the Filter() call in every setter?
private string _MyVal1;
public string MyVal1 {
get {
return _MyVal1;
}
set {
_MyVal1 = value;
Filter();
OnPropertyChanged("MyVal1");
}
}
private string _MyVal2;
public string MyVal2 {
get {
return _MyVal2;
}
set {
_MyVal2 = value;
Filter();
OnPropertyChanged("MyVal2");
}
}
private string _MyValN;
public string MyValN {
get {
return _MyValN;
}
set {
_MyValN = value;
Filter();
OnPropertyChanged("MyValN");
}
}
So it turns into this:
private string _MyValN;
public string MyValN {
get {
return _MyValN;
}
set : FilterSetter {
_MyValN = value;
OnPropertyChanged("MyValN");
}
}
A different way of doing this is to use interception like that provided by the Unity framework. With interception your class implements an interface and you would tell the framework that everytime a method is called on classes implementing that interface, run these interceptors. Your interceptors code can look to see if the method being called is prefixed with set_. Interceptor code executes once on the way to the function and once on the way back. On the way back, you can then have the interceptor call the filter method (assuming it is defined on the interface of course).
Concrete example:
Get Prerequisite Library
Use NuGet to add Unity and Unity extensions to your project
Define your interface to be intercepted: SomeObject.cs
using System;
namespace InterceptSetter
{
interface ISomeObject
{
string SomeProperty { get; set; }
void Filter();
}
public class SomeObject : ISomeObject
{
public string SomeProperty { get; set; }
public void Filter()
{
Console.Out.WriteLine("Filter Called");
}
}
}
Define your Interception Behavior: SetterCallsFilterMethodBehavior.cs
using Microsoft.Practices.Unity.InterceptionExtension;
using System;
using System.Collections.Generic;
using System.Linq;
namespace InterceptSetter
{
/// <summary>
/// See http://msdn.microsoft.com/en-us/library/ff660871(v=pandp.20).aspx
/// See http://msdn.microsoft.com/en-us/library/ff647107.aspx
/// </summary>
class SetterCallsFilterMethodBehavior : IInterceptionBehavior
{
public IEnumerable<Type> GetRequiredInterfaces()
{
// we dont need anything
return new[] { typeof(ISomeObject) };
}
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{ // Do not intercept non-setter methods
if (!input.MethodBase.Name.StartsWith("set_"))
return getNext()(input, getNext);
IMethodReturn msg = getNext()(input, getNext);
// post processing. this is where we call filter
if (input.Target is ISomeObject)
{
(input.Target as ISomeObject).Filter();
}
return msg;
}
/// <summary>
/// We always execute
/// </summary>
public bool WillExecute
{
get { return true; }
}
}
}
Write a test console program: Program.cs
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using System;
namespace InterceptSetter
{
class Program
{
static void Main(string[] args)
{
UnityContainer container = new UnityContainer();
container.AddNewExtension<Interception>();
container.RegisterType<ISomeObject, SomeObject>(
new Interceptor<TransparentProxyInterceptor>(),
new InterceptionBehavior<SetterCallsFilterMethodBehavior>());
// we must get our instance from unity for interception to occur
ISomeObject myObject = container.Resolve<ISomeObject>();
myObject.SomeProperty = "Hello Setter";
Console.ReadLine();
}
}
}
Running this you will see that the interceptor does in fact call the filter method (which prints to the console).
Unity is not the only dependency injection / interception framework out there (google PostSharp). Unity is the one i am familiar with so thats what this example uses.
Sources / See Also:
http://msdn.microsoft.com/en-us/library/ff660871(v=pandp.20).aspx - Good diagram depicting the flow of interception
http://msdn.microsoft.com/en-us/library/ff647107.aspx - overkill of detail showing different interception techniques
You can create generic setter method and call that from each property setter:
private void Set<T>(ref T field, T value, string propertyName)
{
field = value;
Filter();
OnPropertyChanged(propertyName);
}
Then your properties look like:
public string SomeProperty
{
get { return this.someField; }
set
{
Set(ref this.someField, value, "SomeProperty");
}
}

Constructor in ViewModel

Is it possible to have a constructor in the ViewModel, which initializes the data service?
My data service is accessing the web-service of the data storage in a manner similar to this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Commands;
using MobSales.Logic.DataService;
using MobSales.Logic.Base;
using MobSales.Logic.Model;
namespace MobSales.Logic.ViewModels
{
public class CustomersViewModel:MvxViewModel
{
ICustomerService custService;
public CustomersViewModel(ICustomerService custService)
{
this.custService = custService;
if (custService != null)
{
custService.LoadCustomerCompleted += new EventHandler<CustomerLoadedEventArgs>(custService_LoadCustomerCompleted);
}
loadCustomerCommand = new MvxRelayCommand(LoadCustomer);
loadCustomerCommand.Execute();
}
private ObservableCollection<Customer> customers;
public ObservableCollection<Customer> Customers
{
get { return customers; }
set
{
customers = value;
FirePropertyChanged("Customers");
}
}
private CustomerViewModel customer;
public CustomerViewModel Customer
{
get { return customer; }
set
{
customer = value;
FirePropertyChanged("Customer");
}
}
private MvxRelayCommand loadCustomerCommand;
public MvxRelayCommand LoadCustomerCommand
{
get { return loadCustomerCommand; }
}
public void LoadCustomer()
{
custService.LoadCustomer();
}
void custService_LoadCustomerCompleted(object sender, CustomerLoadedEventArgs e)
{
if (e.Error != null)
{
return;
}
List<Customer> loadedCustomers = new List<Customer>();
foreach (var cust in e.Customers)
{
loadedCustomers.Add(new Customer(cust));
}
Customers = new ObservableCollection<Customer>(loadedCustomers);
}
}
I am getting an exception but can only see the following partial description:
Cirrious.MvvmCross.Exceptions.MvxException: Failed to load ViewModel for type MobSales.Logic.ViewModels.CustomersViewModel from locator MvxDefau…
The binding from View to ViewModel is realized as I've shown in this post: MVVMCross Bindings in Android
Thanks!
One of the unusual (opinionated) features of MvvmCross is that by default it uses ViewModel constructor parameters as part of the navigation mechanism.
This is explained with an example in my answer to Passing on variables from ViewModel to another View (MVVMCross)
The basic idea is that when a HomeViewModel requests a navigation using:
private void DoSearch()
{
RequestNavigate<TwitterViewModel>(new { searchTerm = SearchText });
}
then this will cause a TwitterViewModel to be constructed with the searchTerm passed into the constructor:
public TwitterViewModel(string searchTerm)
{
StartSearch(searchTerm);
}
At present, this means that every ViewModel must have a public constructor which has either no parameters or which has only string parameters.
So the reason your ViewModel isn't loading is because the MvxDefaultViewModelLocator can't find a suitable constructor for your ViewModel.
For "services", the MvvmCross framework does provide a simple ioc container which can be most easily accessed using the GetService<IServiceType>() extension methods. For example, in the Twitter sample one of the ViewModel contains:
public class TwitterViewModel
: MvxViewModel
, IMvxServiceConsumer<ITwitterSearchProvider>
{
public TwitterViewModel(string searchTerm)
{
StartSearch(searchTerm);
}
private ITwitterSearchProvider TwitterSearchProvider
{
get { return this.GetService<ITwitterSearchProvider>(); }
}
private void StartSearch(string searchTerm)
{
if (IsSearching)
return;
IsSearching = true;
TwitterSearchProvider.StartAsyncSearch(searchTerm, Success, Error);
}
// ...
}
Similarly, you can see how the conference service data is consumed in the Conference BaseViewModel
If your preference is to use some other IoC container or some other construction mechanism for your ViewModels, then you can override the ViewModel construction within MvvmCross.
Take a look at this question (and answers) for ideas on how to do this - How to replace MvxDefaultViewModelLocator in MVVMCross application
e.g. if you want to, then it should be fairly easy for you to adjust the MyViewModelLocator example in that question to construct your ViewModel with your service.

Component Communication at Design Time

I want to have a "form" or something else (no matter what) that can add string to a List
it would be done with a First Component "StringManager" witch contains the string collection
On the other hand i want to have another Component "ComponentReader" that use IExtenderProvider and add on All controls (on the component form) a property named "TheString" which let me choose in one of the string from List
So, to be clear : i want to share the List<String> with the minimum of code on each forms, (the most with properties editor)
I don't know how can i tell the "ComponentReader" where is the main component that he refers,
(i 've add a property ReferedStringManager in my "ComponentReader").
Is there any properties or instruction (or way)to inspect the project and his references to get all matchable value as List in "ComponentReader" properties
for the ReferedStringManager property of the ComponentReader;
If its not possible ,i think of Static List or something else, maybe XML file, (but i don't know how to manage that during conception )
Of course all of that is at Design Time, not at Execution Time (it would be so simpler !!)
this is a late answer, but if you're still interested, here's how to do it.
There are quite a few requirements for both your StringManager and your ComponentReader classes.
1) both classes need to derive from System.ComponentModel.Component.
2) The StringManager must override the Site property of the Component class. This is what makes it "VS Designer aware", and allows it to be selectable in your ComponentReader's properties later on.
3) The StringManager must expose the List as a public property. For convenience, in my sample code bolow, I have set the EditorAttribute for easy string collection edition in the VS property grid.
4) The ComponentReader must implement a custom interface with only one property of type StringManager. This is needed by the requirement #6.
5) The ComponentReader must have a public property of type StringManager
6) The ComponentReader must have a public property of type string for the SelectedString. The catch is that we must set the TypeConverterAttribute to yet another class that we must implenent (see #7)
7) We must implement a StringConverter derived class. This will be used to allow us to select a string from the selected StringManager. Within this class, we must be able to retrieve a reference to the ComponentReader through the interface created in #4.
Now, for the code:
StringManager.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Windows.Forms;
using System.ComponentModel.Design;
namespace VSDesignHost
{
class StringManager : Component
{
private ContainerControl _containerControl = null;
public StringManager()
{
TheList = new List<string>();
}
public StringManager(IContainer container) : this()
{
container.Add(this);
InitializeComponent();
}
private void InitializeComponent()
{
//Whatever
}
public ContainerControl ContainerControl
{
get { return _containerControl; }
set { _containerControl = value; }
}
public override ISite Site
{
set
{
base.Site = value;
if (value != null)
{
IDesignerHost host = value.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (host != null)
{
IComponent rootComponent = host.RootComponent;
if (rootComponent is ContainerControl)
{
this.ContainerControl = (ContainerControl)rootComponent;
}
}
}
}
}
[Editor("System.Windows.Forms.Design.StringCollectionEditor, System.Design", "System.Drawing.Design.UITypeEditor, System.Drawing")]
public List<string> TheList { get; set; }
}
}
ComponentReader.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace VSDesignHost
{
class ComponentReader : Component, IStringManagerEnabled
{
private StringManager sm;
public ComponentReader()
{
sm = null;
}
[Browsable(true), Category("MyCategory")]
public StringManager StringManager
{
get { return sm; }
set
{
sm = value;
}
}
[Browsable(true), Category("MyCategory"), TypeConverter(typeof(StringManagerStringConverter))]
public string SelectedString { get; set; }
}
}
IStringManagerEnabled.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace VSDesignHost
{
interface IStringManagerEnabled
{
StringManager StringManager
{
get;
set;
}
}
}
StringManagerStringConverter.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace VSDesignHost
{
class StringManagerStringConverter : StringConverter
{
#region Make It A ComboBox
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
{
return false;
}
#endregion
#region Display Tags In List
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
if ((context == null) || (context.Container == null))
{
return null;
}
Object[] Tags = this.GetTagsFromServer(context);
if (Tags != null)
{
return new StandardValuesCollection(Tags);
}
return null;
}
private object[] GetTagsFromServer(ITypeDescriptorContext context)
{
List<string> availableTags = new List<string>();
if (context.Instance == null)
{
availableTags.Add("ITypeDescriptorContext.Instance is null");
return availableTags.ToArray();
}
IStringManagerEnabled inst = context.Instance as IStringManagerEnabled;
if (inst == null)
{
availableTags.Add(context.Instance.ToString());
return availableTags.ToArray();
}
if (inst.StringManager == null)
{
availableTags.Add("No StringManager selected");
return availableTags.ToArray();
}
availableTags = inst.StringManager.TheList;
availableTags.Sort(Comparer<string>.Default);
return availableTags.ToArray();
}
#endregion
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
return value.ToString();
return base.ConvertFrom(context, culture, value);
}
}
}
Create a Windows Forms project, add these files to it, and build the project.
Now, on you Form, you can add an instance of each the StringManager and ComponentReader by dragging from the toolbox.
Select the StringManager, and in the properties, add a few string to TheList by using the string collection editor.
Select the ComponentReader, and in the properties windows, you should be able to select your StringManager instance from the dropdown list.
You should now be able to select one of the string from the dropdown for SelectedString.
I hope you enjoy
Luc

Categories

Resources