NotifyPropertyChange fired but UI field not updated in Xamarin.Forms - xaml

I'm currently refactoring a few abstraction layers into a Xamarin app in order to break the "monolithic" structure left by the previous dev, but something has gone awry. In my ViewModel, I have a few properties that call NotifyPropertyChange in order to update the UI whenever a value is picked from a list. Like so:
public Notifier : BindableObject, INotifyPropertyChanged
{
//...
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Had to create a middle layer due to my specific needs
public interface ISomeArea
{
DefinicaoServicoMobile TipoPasseio { get; set; }
}
-
public class SomeAreaImpl : Notifier, ISomeArea
{
//...
protected DefinicaoServicoMobile _tipoPasseio;
public DefinicaoServicoMobile TipoPasseio
{
get => _tipoPasseio;
set
{
if (_tipoPasseio != value)
{
_tipoPasseio = value;
NotifyPropertyChanged(nameof(TipoPasseio));
}
}
}
}
The actual bound view model:
public MyViewModel : BaseViewModel, ISomeArea
{
private SomeAreaImpl someArea;
//...
public MyViewModel()
{
// This is meant to provide interchangable areas across view models with minimal code replication
someArea = new SomeAreaImpl();
}
public DefinicaoServicoMobile TipoPasseio
{
get => someArea.TipoPasseio;
set => someArea.TipoPasseio = value;
}
}
And the .xaml snippet:
<renderers:Entry
x:Name="TxtTipoPasseio"
VerticalOptions="Center"
HeightRequest="60"
HorizontalOptions="FillAndExpand"
Text="{Binding TipoPasseio.DsPadrao}"
/>
The renderer opens a list allowing the user to choose whichever "TipoPasseio" they want, and supposedly fill the textbox with a DsPadrao (standard description). Everything works, even the reference to TipoPasseio is held after being selected (I know this because should I bring up the list a second time, it will only display the selected DsPadrao, giving the user the option to clean it. If he does, a third tap will show all the options again.
I might have screwed up in the abstraction, as I don't see the setter for myViewModel.TipoPasseio being called, tbh
Any ideas?

Let's reason through what Xamarin knows (as best as we can, since you didn't include all of the relevant code):
You have a data context having the type MyViewModel
That view model object has a property named TipoPasseio, having type DefinicaoServicoMobile
The type DefinicaoServicoMobile has a property named DsPadrao
It is that last property that is bound to the Entry.Text property.
In a binding, any observable changes to values forming the source or path for the binding will cause the runtime to update the target property for the binding (Entry.Text) and thus result in a change in the visual appearance (i.e. new text being displayed).
Note the key word observable. Here are the things I see which are observable by Xamarin:
The data context. But this doesn't change.
That's it.
With respect to the value of the MyViewModel.TipoPasseio property, there's nothing in the code you posted showing this property changing. But if it did, it doesn't look like MyViewModel implements INotifyPropertyChanged, so Xamarin wouldn't have a way to observe such a change.
On that second point, you do implement INotifyPropertyChanged in the SomeAreaImpl type. But Xamarin doesn't know anything about that object. It has no reference to it, and so has no way to subscribe to its PropertyChanged event.
Based on your statement:
I don't see the setter for myViewModel.TipoPasseio being called
That suggests that the TipoPasseio property isn't being changed. I.e. while you wouldn't be providing notification to Xamarin even if it did change, it's not changing anyway.
One property that does seem to be changing is the DsPadrao property (after all, it's the property that's actually providing the value for the binding). And while you don't provide enough details for us to know for sure, it seems like a reasonable guess that the DefinicaoServicoMobile doesn't implement INotifyPropertyChanged, and so there's no way for Xamarin to ever find out the value of that property might have changed either.
In other words, of all the things that Xamarin can see, the only one that it would be notified about of a change is the data context. And that doesn't seem to be what's changing in your scenario. None of the other values are held by properties backed by INotifyPropertyChanged.
Without a complete code example, it's impossible to know for sure what the right fix is. Depending on what's changing and how though, you need to implement INotifyPropertyChanged for one or more of your types that don't currently do so.

As it turns out, I wasn't firing the NotifyPropertyChanged of the correct object. Both MyViewModel and SomeAreaImpl implemented INotifyPropertyChanged per the Notifier class as BaseViewModel also extends from Notifier but that ended up ommited in my question. Having figured that out, here's an working (and complete) example:
public Notifier : BindableObject, INotifyPropertyChanged
{
//...
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Specifics about DefinicaoServicoMobile are negligible to this issue
public interface ISomeArea
{
//...
DefinicaoServicoMobile TipoPasseio { get; set; }
Task SetServico(ServicoMobile servicoAtual;
//...
}
For the sake of clarification
public abstract class BaseViewModel : Notifier
{
protected abstract Task SetServico(ServicoMobile servicoAtual);
public async Task SetServico()
{
//...
await SetServico(servicoAtual);
//...
}
}
Changed a couple of things here. It no longer extends from Notifier, which was kinda weird to begin with. Also this is where I assign TipoPasseio
public class SomeAreaImpl : ISomeArea
{
//...
protected DefinicaoServicoMobile _tipoPasseio;
// I need to call the viewModel's Notifier, as this is the bound object
private BaseViewModel viewModel;
public AreaServicosDependentesImpl(BaseViewModel viewModel)
{
this.viewModel = viewModel;
}
public DefinicaoServicoMobile TipoPasseio
{
get => _tipoPasseio;
set
{
if (_tipoPasseio != value)
{
_tipoPasseio = value;
viewModel.NotifyPropertyChanged(nameof(TipoPasseio));
}
}
}
//Assigning to the property
public async Task SetServico(ServicoMobile servicoAtual, List<DefinicaoServicoMobile> listDefinicaoServico)
{
//...
TipoPasseio = listDefinicaoServico
.FirstOrDefault(x => x.CdServico == servicoAtual.TpPasseio.Value);
//...
}
}
Changes to the view model:
public MyViewModel : BaseViewModel, ISomeArea
{
private SomeAreaImpl someArea;
//...
public MyViewModel()
{
someArea = new SomeAreaImpl(this);
}
public DefinicaoServicoMobile TipoPasseio
{
get => someArea.TipoPasseio;
set => someArea.TipoPasseio = value;
}
protected override async Task SetServico(ServicoMobile servicoAtual)
{
//...
someArea.SetServico(servicoAtual, ListDefinicaoServico.ToList());
//...
}
}
View model binding
public abstract class BaseEncerrarPontoRotaPage : BasePage
{
private Type viewModelRuntimeType;
public BaseEncerrarPontoRotaPage(Type viewModelRuntimeType)
{
this.viewModelRuntimeType = viewModelRuntimeType;
}
private async Task BindContext(PontoRotaMobile pontoRota, ServicoMovelMobile servicoMovel, bool finalizar)
{
_viewModel = (BaseViewModel)Activator.CreateInstance(viewModelRuntimeType, new object[] { pontoRota, UserDialogs.Instance });
//...
await _viewModel.SetServico();
//...
BindingContext = _viewModel;
}
public static BaseEncerrarPontoRotaPage Create(EnumAcaoServicoType enumType)
{
Type pageType = enumType.GetCustomAttribute<EnumAcaoServicoType, PageRuntimeTypeAttribute>();
Type viewModelType = enumType.GetCustomAttribute<EnumAcaoServicoType, ViewModelRuntimeTypeAttribute>();
return (BaseEncerrarPontoRotaPage)Activator.CreateInstance(pageType, new object[] { viewModelType });
}
}
Page instantiation is performed in some other view model, not related to the structure presented here
private async Task ShowEdit(bool finalizar)
{
await Task.Run(async () =>
{
var idAcaoServico = ServicoMobileAtual.DefinicaoServicoMobile.IdAcaoServico;
var page = BaseEncerrarPontoRotaPage.Create((EnumAcaoServicoType)idAcaoServico);
await page.BindContext(PontoRotaAtual, ServicoMovelMobileAtual, finalizar);
BeginInvokeOnMainThread(async () =>
{
await App.Navigation.PushAsync(page);
});
});
}
Codebehind:
public partial class MyPage : BaseEncerrarPontoRotaPage
{
public NormalUnidadePage() { }
public MyPage(Type viewModelType) : base(viewModelType)
{
InitializeComponent();
//Subscription to show the list
TxtTipoPasseio.Focused += TxtTipoPasseio_OnFocused;
//...
}
}
XAML
<views:BaseEncerrarPontoRotaPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:My.Name.Space.;assembly=Phoenix.AS"
x:Class="My.Name.Space.MyPage">
//...
<renderers:Entry
x:Name="TxtTipoPasseio"
VerticalOptions="Center"
HeightRequest="60"
HorizontalOptions="FillAndExpand"
Text="{Binding TipoPasseio.DsPadrao}"/>
//...
</views:BaseEncerrarPontoRotaPage>
I know could propagate an event from the AreaImpl classes in order to fire the Notify event in the view model, but right now I'm satisfied with this solution.

Related

.Net Maui Shell Navigation - Is it possible to pass a Query Parameter and Auto Populate a Page?

I need to auto populate a Page by passing a Shell Navigation Parameter to a ViewModel/Method and call a Service to return a single record from a Web Service. Essentially a drill-through page. My issue is that I need to call the data retrieveal command, "GetFieldPerformanceAsync" (note [ICommand] converts this to "GetFieldPerformanceCommand") from the "To" Page's code-behind from within OnNavigatedTo. This is required since the Shell Navigation Parameter is not set in the ViewModel until the Page is loaded. I'm currently unable to make the Command call from OnNavigatedTo and need advice on how to accomplish this.
Thanks!
Code behind the Page:
public partial class FieldPerformancePage : ContentPage
{
public FieldPerformancePage(FieldPerformanceViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
//works with parameter hard-coded in ViewModel
//viewModel.GetFieldPerformanceCommand.Execute(null);
}
FieldPerformanceViewModel viewModel;
protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
//this does not work
viewModel.GetFieldPerformanceCommand.Execute(null);
}
}
ViewModel
namespace TrackMate.ViewModels;
[QueryProperty(nameof(FieldAssignedWbs), nameof(FieldAssignedWbs))]
public partial class FieldPerformanceViewModel : BaseViewModel
{
[ObservableProperty]
FieldAssignedWbs fieldAssignedWbs;
[ObservableProperty]
FieldPerformance fieldPerformance;
FieldPerformanceService fieldPerformanceService;
public FieldPerformanceViewModel(FieldPerformanceService fieldStatusService)
{
Title = "Status";
this.fieldPerformanceService = fieldStatusService;
}
[ICommand]
async Task GetFieldPerformanceAsync()
{
if (IsBusy)
return;
try
{
IsBusy = true;
int wbsId = fieldAssignedWbs.WbsId;
var fieldPerformanceList = await fieldPerformanceService.GetFieldPerformanceList(wbsId);
if (fieldPerformanceList.Count != 0)
FieldPerformance = fieldPerformanceList.First();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
await Shell.Current.DisplayAlert("Error!",
$"Undable to return records: {ex.Message}", "OK");
}
finally
{
IsBusy = false;
}
}
}
I believe I figured it out...
By adding ViewModel Binding within the OnNavigatedTo method in the "DetailsPage" Code Behind, a Command Call can be made to the Page's ViewModel to execute data retrieval method after the Shell Navigation Parameter (object in this scenario) passed from the "Main" Page has been set. Note a null is passed since the Query Parameter is sourced from the ViewModel. If you are new to .Net Maui, as I am, I recommend James Montemagno's video on .Net Maui Shell Navigation.
namespace TrackMate.Views;
public partial class FieldPerformancePage : ContentPage
{
public FieldPerformancePage(FieldPerformanceViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
FieldPerformanceViewModel viewModel = (FieldPerformanceViewModel)BindingContext;
viewModel.GetFieldPerformanceCommand.Execute(null);
base.OnNavigatedTo(args);
}
}
For me it only worked when the BindingContext assignment is before the component initialization and the method call after the base call in OnNavigatedTo
public partial class OccurrencePage : ContentPage
{
public OccurrencePage(OccurrenceViewModel model)
{
BindingContext = model;
InitializeComponent();
}
protected override void OnNavigatedTo(NavigatedToEventArgs args)
{
base.OnNavigatedTo(args);
OccurrenceViewModel viewModel = (OccurrenceViewModel)BindingContext;
viewModel.GetFieldsCommand.Execute(null);
}
}
While overriding OnNavigatedTo works fine, there is one more simple technique to run something once your query param is set, given you do not need to run anything asynchronous inside the method: implementing partial method OnFieldAssignedWbsChanged, auto-generated for your convenience by mvvm toolkit
partial void OnFieldAssignedWbsChanged(FieldAssignedWbs value)
{
// run synchronous post query param set actions here
}
Less amount of code and less code-behind and viewModel dependencies, but works fine for non-async operations only.

Xamarin.Forms change UI language at runtime (XAML)

I am using Strings.resx, Strings.de.resx, etc. to localize Xamarin.Forms app.
I need to be able to change interface language at run time, and it (allmost) works.
Xamarin generates static class Strings in namespace MyProject.Resources from resource files, and I use those values to display strings on UI.
When doing it from code, it works flawlessly:
await DisplayAlert(Strings.lblConfirmDelete, Strings.lblDeleteMessage, Strings.lblOK, Strings.lblCancel));
Problem is - not all attributes defined this way from XAML are updated when I change UI culture during runtime.
Buttons, Labels, Entry properties (Placeholder etc.) change as they should, but PageTitle, Toolbaritems, and some other properties remain in previous language.
I presume that some of these are populated when Page is first created, and are not updated on culture (and UI culture) change.
So, basically, I need a way to combine {DynamicResource ...} with values from resources.
I know that DynamicResource is ment to be used with Resource dictionary, but that is not a good way to store language translations for localization.
I tried
Text="{DynamicResource {x:Static lr:Strings.lblAddNew}}"
also not working.
Is there a way of refreshing page dynamicaly?
I also tried calling
global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(MainListPage));
from Appearing event for that page, but that also does not work.
Any ideas?
Part of XAML file
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MyProject.View"
xmlns:rs="clr-namespace:MMPI"
x:Class="MyProject.MainListPage"
xmlns:lr="clr-namespace:MyProject.Resources"
Title="{x:Static lr:Strings.appName}"
>
<ContentPage.ToolbarItems>
<ToolbarItem
Name="New"
Order="Primary"
Priority="0"
Text="{x:Static lr:Strings.lblAddNew}"
Clicked="New_Clicked"
>
When i encountered that challenge in a project I resolved it by using a simple class ResourceLoader and making use of INotifyPropertyChanged.
You can access the Instanceproperty from anywhere and change the culture. All String that are bound to the index would update.
The ResourceManager instance injected into the constructor must be set up appropriately.
public class ResourceLoader : INotifyPropertyChanged
{
private readonly ResourceManager manager;
private CultureInfo cultureInfo;
public ResourceLoader(ResourceManager resourceManager)
{
this.manager = resourceManager;
Instance = this;
this.cultureInfo = CultureInfo.CurrentUICulture;
}
public static ResourceLoader Instance { get; private set; }
public string GetString(string resourceName)
{
string stringRes = this.manager.GetString(resourceName, this.cultureInfo);
return stringRes;
}
public string this[string key] => this.GetString(key);
public void SetCultureInfo(CultureInfo cultureInfo)
{
this.cultureInfo = cultureInfo;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null));
}
public event PropertyChangedEventHandler PropertyChanged;
}
To display the localized strings in your application you need to bind through the indexer like so:
<Label Text="{Binding [Test], Source={x:Static ResourceLoader.Instance}}" />
Since it is now bound it should update when you call ResourceLoader.SetCultureInfo because the Item[] 'PropertyName' is causing bound controls to re-fetch the values to their bound keys.
Update
I just tested it if i was talking bogus and for some reason the property changed didn't work. I've added a different approach below, which is close to what i'm using in production i urge you to add some kind of weak reference 'caching' instead of the simple list holding all the string resources (otherwise they will be kept forever)
I'm keeping above for reference.
public class ResourceLoader
{
public ResourceLoader(ResourceManager resourceManager)
{
this.manager = resourceManager;
Instance = this;
this.cultureInfo = CultureInfo.CurrentUICulture;
}
private readonly ResourceManager manager;
private CultureInfo cultureInfo;
private readonly List<StringResource> resources = new List<StringResource>();
public static ResourceLoader Instance { get; private set; }
public StringResource this[string key] {
get { return this.GetString(key); }
}
public StringResource GetString(string resourceName)
{
string stringRes = this.manager.GetString(resourceName, this.cultureInfo);
var stringResource = new StringResource(resourceName, stringRes);
this.resources.Add(stringResource);
return stringResource;
}
public void SetCultureInfo(CultureInfo cultureInfo)
{
this.cultureInfo = cultureInfo;
foreach (StringResource stringResource in this.resources) {
stringResource.Value = this.manager.GetString(stringResource.Key, cultureInfo);
}
}
}
StringResource:
public class StringResource : INotifyPropertyChanged
{
public StringResource(string key, string value)
{
this.Key = key;
this.Value = value;
}
private string value;
public string Key { get; }
public string Value {
get { return this.value; }
set {
this.value = value;
this.OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML Binding
<Label Text="{Binding [ResourceKey].Value, Mode=OneWay, Source={x:Static local:ResourceLoader.Instance}}"
/>
Update 2
Came across this link where they implemented it similarly to my first approach. Maybe you can give it a try.
Update 3
Fixed the first approach. Both are working now. What was needed was this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(null)); instead of this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(Item[]));
I solved it very similar to #woelliJ . I just wanted to have key as strongly types from static class and binding should be in code behind.
ITranslationService is singleton from static variable. It is very close like #woelliJ .
[ContentProperty("Text")]
public sealed class TranslateExtension : IMarkupExtension<BindingBase>
{
private readonly ITranslationService? _translationService;
public TranslateExtension()
{
_translationService = Mobile.App.TranslationService;
}
public string? Text { get; set; }
public BindingBase ProvideValue(IServiceProvider serviceProvider)
{
var translationItem = _translationService[Text];
var binding = new Binding
{
Mode = BindingMode.OneWay,
Path = $"Value",
Source = translationItem,
};
return binding;
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
}
}
[AddINotifyPropertyChangedInterface]
public class TranslationItem
{
public string? Key { get; set; }
public string? Value { get; set; }
}
Then label would be like this
<Label FontSize="Title" Text="{services:Translate Text={x:Static models:M.AboutTestInfoTitle}}" />

NInject IBindingGenerator and ToProvider

I've created this code:
public class AddonsModule : Ninject.Modules.NinjectModule
{
public override void Load()
{
this.Bind(b => b.FromAssembliesMatching("*")
.SelectAllClasses()
.InheritedFrom(typeof(UIExtensibility.AbstractAddon))
.BindWith(new AddonBindingGenerator())
);
}
private class AddonBindingGenerator : IBindingGenerator
{
public System.Collections.Generic.IEnumerable<Ninject.Syntax.IBindingWhenInNamedWithOrOnSyntax<object>> CreateBindings(System.Type type, Ninject.Syntax.IBindingRoot bindingRoot)
{
if (type.IsInterface || type.IsAbstract)
yield break;
yield return bindingRoot.Bind(type).ToProvider(typeof(UIExtensibility.AbstractAddon));
}
}
private class AddonProvider : IProvider<UIExtensibility.AbstractAddon>
{
public object Create(IContext context)
{
return null;
}
public Type Type
{
get { throw new NotImplementedException(); }
}
}
}
AddonProvider seems be avoided. This is never performed.
When I perform:
kernel.GetAll<UIExtensibility.AbstractAddon>(), AddonProvider.Create method is never performed.
Could you tell me what's wrong?
I'll appreciate a lot your help.
Thanks for all.
AddOnProvider is inheriting from IProvider<T> instead of UIExtensibility.AbstractAddon.
also, you may have issues binding to private inner classes. make AddOnProvider a public top level class.
You're binding a specific type which inherits from typeof(UIExtensibility.AbstractAddon) to a provider. For example, there could be a class Foo : UIExtensibility.AbstractAddon.
Now your convention binding translates to this:
Bind<Foo>().ToProvider<AddonProvider>();
Now, kernel.GetAll<UIExtensibility.AbstractAddon>() however is looking for bindings made like:
Bind<UIExtensibility.AbstractAddon>().To...
Fix It
So what you need to do is change the line
bindingRoot.Bind(type).ToProvider(new AddonProvider());
to:
bindingRoot.Bind(typeof(UIExtensibility.AbstractAddon)).ToProvider<AddonProvider>();
Furthermore
you're line object f = bindingRoot.Bind(type).ToProvider(new AddonProvider()); is never returning the binding (object f).
does UIExtensibility.AbstractAddon implement IProvider?
Thanks for your answer and comments.
I believe the trouble is on I'm not quite figuring out how this "generic" binding process works.
I'm going to try writing my brain steps process out:
I need to bind every AbstractAddon implementation inside addons assemblies folder. So, I think this code is right, but I'm not sure at all.
this.Bind(b => b.FromAssembliesMatching("*")
.SelectAllClasses()
.InheritedFrom(typeof(UIExtensibility.AbstractAddon))
.BindWith(new AddonBindingGenerator())
);
My AbstractAddon is like:
public abstract class AbstractAddon : IAddon
{
private object configuration;
public AbstractAddon(object configuration)
{
this.configuration = configuration;
}
// IAddon interface
public abstract string PluginId { get; }
public abstract string PluginVersion { get; }
public abstract string getCaption(string key);
public abstract Type getConfigurationPanelType();
public abstract System.Windows.Forms.UserControl createConfigurationPanel();
}
I guess I need to:
foreach implementation of `AbstractAddon` found out,
I need to "inject" a configuration object ->
So, I guess I need to set a provider and provide this configuration object.
This would be my main way of thinking in order to solve this problem.
I've changed a bit my first approach. Instead of using a IBindingGenerator class, I've used the next:
public class AddonsModule : Ninject.Modules.NinjectModule
{
public override void Load()
{
this.Bind(b => b.FromAssembliesMatching("*")
.SelectAllClasses()
.InheritedFrom(typeof(UIExtensibility.AbstractAddon))
.BindAllBaseClasses()
.Configure(c => c.InSingletonScope())
);
this.Bind<object>().ToProvider<ConfigurationProvider>()
.WhenTargetHas<UIExtensibility.ConfigurationAttribute>();
}
So, My ConfigurationProvider is:
private class ConfigurationProvider : IProvider<object>
{
public object Create(IContext context)
{
return "configuration settings";
}
}
And now, my AbstractAddon constructor contains the parameter annotated with ConfigurationAttribute as:
public AbstractAddon([Configuration]object configuration)
{
this.configuration = configuration;
}
The problem now, NInject seems to ignore the configuration object provider. NInject generates a dump object, however, not perform ConfigurationProvider.Create method...
What I'm doing wrong, now?
Is this approach really better than the last one?
Thanks for all.

Any WinRT iCommand/CommandBinding implementaiton samples out there?

Abstracting commands into the View Model is a valuable practice with XAML/MVVM projects. I get that. And, I see ICommand in in WinRT; but, how do we implement it? I haven't found a sample that actually works. Anyone know?
My all time favorite has to be the DelegateCommand provided by the Microsoft Patterns and Practices team. It allows you to create a typed command:
MyCommand = new DelegateCommand<MyEntity>(OnExecute);
...
private void OnExecute(MyEntity entity)
{...}
It also provides a way to raise the CanExecuteChanged event (to disable/enable the command)
MyCommand.RaiseCanExecuteChanged();
Here's the code:
public class DelegateCommand<T> : ICommand
{
private readonly Func<T, bool> _canExecuteMethod;
private readonly Action<T> _executeMethod;
#region Constructors
public DelegateCommand(Action<T> executeMethod)
: this(executeMethod, null)
{
}
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
{
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
}
#endregion Constructors
#region ICommand Members
public event EventHandler CanExecuteChanged;
bool ICommand.CanExecute(object parameter)
{
try
{
return CanExecute((T)parameter);
}
catch { return false; }
}
void ICommand.Execute(object parameter)
{
Execute((T)parameter);
}
#endregion ICommand Members
#region Public Methods
public bool CanExecute(T parameter)
{
return ((_canExecuteMethod == null) || _canExecuteMethod(parameter));
}
public void Execute(T parameter)
{
if (_executeMethod != null)
{
_executeMethod(parameter);
}
}
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged(EventArgs.Empty);
}
#endregion Public Methods
#region Protected Methods
protected virtual void OnCanExecuteChanged(EventArgs e)
{
var handler = CanExecuteChanged;
if (handler != null)
{
handler(this, e);
}
}
#endregion Protected Methods
}
Check out RelayCommand class (only METRO code). The NotifyPropertyChanged class can be found here. The NotifyPropertyChanged class is only used to allow bindings on CanExecute and update it with RaiseCanExecuteChanged.
The original relay command class can be found here
Unfortunately there does not seem to be a native class that implements it for you. The interface is not overly complicated if you want to implement it yourself, and the popular MVVM Lite toolkit includes its own version of RelayCommand. You can add MVVM Lite to your project by right-clicking on References and choosing "Manage NuGet Packages". If you don't have this option, enable Nuget under Tools -> Extensions and Updates.
I've been looking for a minimal end-to-end implementation of a XAML-MVVM command, and not found it yet.
So, following #Rico's answer I ended up with the following as a minimal RelayCommand which works. I use a similar minimal version in a large project.
public class RelayCommand : System.Windows.Input.ICommand {
readonly Action<object> execute;
public RelayCommand(Action<object> execute) {
this.execute = execute;
}
public bool CanExecute(object parameter) {
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter) {
this.execute(parameter);
}
}
The larger RelayCommand class seems to provide more control over CanExecute and CanExecuteChanged, but you don't need that to get started - and you may not need it at all.
To use it in a view model:
class ViewModel : INotifyPropertyChanged {
<< ... snip VM properties and notifications ...>>
public RelayCommand DoSomethingCommand {
get {
return new RelayCommand(param => {
this.DoSomething(param as AType);
Debug.WriteLine("Command Executed");
});
}
}
}
(We don't need the INotifyPropertyChanged for the Command, but any view model typically implements it.)
Finally, the XAML...
<Grid>
<!-- Set the data context here, for illustration. -->
<Grid.DataContext>
<local:ViewModel/>
</Grid.DataContext>
<!-- A sample control bind to a property -->
<TextBlock
Text="{Binding AProp}"/>
<!-- Bind a command -->
<Button Command="{Binding DoSomethingCommand}" CommandParameter="foo">Change!</Button>
</Grid>
I found this really good example at
https://code.msdn.microsoft.com/windowsapps/Working-with-ICommand-690ba1d4
<Page.Resources>
<local:MyCommandsCollection x:Key="MyCommands" />
</Page.Resources>
<Button Width="280"
Height="59"
Margin="513,280,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Command="{Binding MyFirstCommand}"
CommandParameter="{Binding Text,
ElementName=myTextBox}"
Content="Execute Command" />
public class MyCommandsCollection
{
public MyCommand MyFirstCommand
{
get { return new MyCommand(); }
}
}
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public async void Execute(object parameter)
{
MessageDialog message = new MessageDialog(
"The command is executing, the value of the TextBox is " + parameter as String);
await message.ShowAsync();
}
}
I tried this out with x:Bind and it works nicely. All I need is to expose a property in my ViewModel that returns a new Instance of the "MyCommand" class and it's all good.
Since I'm setting the DataContext in my XAML, I didn't need to mess with any of the "MyCommandCollection" stuff. Yay compiled binding.

DelegateCommand creation, in Constructor or in Property

I've seen multiple examples of DelegateCommands being created in either the constructor or within the property itself. I'm wondering if there is any advantage of doing it in the constructor as I've been doing it within the property to keep track of it easier.
(Using Prism, Silverlight4 and SimpleMVVM Toolkit in my case)
private DelegateCommand _cmdLogin;
public DelegateCommand CmdLogin
{
get
{
if (_cmdLogin == null)
{
_cmdLogin = new DelegateCommand(this.Login, this.CanLogIn);
}
return _cmdLogin;
}
}
VS
public LoginViewModel()
{
this.LoginCommand = new DelegateCommand(this.Login, this.CanLogin);
}
public DelegateCommand LoginCommand { get; set; }
I have had the same thought as you Suiko6272 on this.
I ended up going with your second solution in the end. however i did use this mechanism in my property gets for quite a while
private DelegateCommand _cmdLogin;
public DelegateCommand CmdLogin
{
get { return _cmdLogin??(_cmdLogin = new DelegateCommand(this.Login, this.CanLogIn));}
}
The above code lazy loads the delegatecommand and is only 1 line of code.
I ended up going with your second solution because it is the clearest/easiest for other coders to read.