In my ViewModel there are two different constructors:
public ViewModel()
{
}
public ViewModel(View View)
{
if (View!=null)
{
}
}
I am using a RelayCommand and in the command binding I want to call the second constructor on button click. How can I call the second constructor and how do I pass the view in ViewModel.
Related
We have a Xamarin.Forms app with FreshMvvm. Now, as Xamarin.Forms will not get support beginning next year, I am re-writing the app with .Net Maui. For MVVM pattern, I am trying to use CommunityToolkit.Mvvm. But I wonder how I can initialize the viewmodel now. With FreshMvvm I could override Init(), but CommunityToolkit.Mvvm does not seem to have anything like this. What is the right way to initialize the viewmodel asynchronously, as there is no async constructor?
In FreshMVVM, since the model has impelmented the FreshBasePageModel, you can override the Init() method and initialize data like the pseudo code below:
public override void Init (object initData)
{
//initialize data
}
However, in CommunityToolkit.Mvvm, you can set the data whatever you want in the default constructor like below:
public partial class MainPage : ContentPage
{
public MainPage(MainPageViewModel vm)
{
InitializeComponent();
BindingContext = vm;
Initialize();
}
public async void Initialize()
{
//await operation
}
}
Official reference link:https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/introduction
I have 2 presenters/view. Lets call them parent and child. parent presenter is a container (using slot mechanism) for the child presenter.
In the child presenter's view user clicked button and I would like to handle this action in parent presenter.
How can I do this? What is the best approach? Should I use some kind of event and eventhandler? Or should I inject one presenter to the other?
Both are possible.
For events - GWTP have a simplified version of GWT events:
public interface MyEventHandler extends EventHandler {
void onMyEvent(MyEvent event);
}
public class MyEvent extends GwtEvent<MyEventHandler> {
public static Type<MyEventHandler> TYPE = new Type<MyEventHandler>();
private Object myData;
public Type<MyEventHandler> getAssociatedType() {
return TYPE;
}
protected void dispatch(MyEventHandler handler) {
handler.onMyEvent(this);
}
public MyEvent(Object myData) {
this.myData = myData;
}
/*The cool thing*/
public static void fire(HasHandlers source, Object myData){
source.fireEvent(new MyEvent(myData));
}
}
So in your child presenter you'll simply do:
MyEvent.fire(this, thatObjectYoudLikeToPass);
and to register it, in the parent, you'd either use:
addRegisteredHandler(MyEvent.TYPE, handler);
or
addVisibleHandler(MyEvent.TYPE, handler);
if you want it to be processed only when the parent is visible. I suggest you yo add these handlers in onBind method of your presenter (don't forget to call super.onBind() first when overriding)
For injection:
Simply make sure:
Your parent presenter is a singleton
To avoid circular dependency error in GIN do not wire it like
#Inject ParentPresenter presenter;
instead do it like this:
#Inject
Provider<ParentPresenter> presenterProvider;
and access it with presenterProvider.get() in your child
I am trying to create an application on the basis of the WAF framework following the MVVM pattern. Currently, my solution consists of two projects (each equipped with MEF and MAF references):
*.Application (holding controllers and viewmodels)
*.Presentation (holding the actual view files)
I am creating the binding between view and viewmodel via the ViewModel interface - see code fragments below. Further, all classes are made available via the MEF framework inside the App.xaml.cs file. Here, the controller is also initialized. In the easiest case, I want to show a string value in a label of the main window.
Here is the problem: If I start the application, the value of the second label only shows the fallback value, but the get method of the property is being called properly (checked via debugging mode). The binding between View and ViewModel seems to be correct - if I change the binding path in the xaml to a non existent property, I get an output that the property can not be found in the ViewModel. My impression is that there could be a problem with the events for view updating? Any suggestions on this strange behaviour?
Here is the expert of the ViewModel:
[Export]
public class MainWindowViewModel : ViewModel<IMainWindowView>
{
private string _labelContent;
public string LabelContent
{
get { return _labelContent; }
set { SetProperty(ref _labelContent, value); }
}
[ImportingConstructor]
public MainWindowViewModel(IMainWindowView view) : base(view)
{
}
}
Here is the exerpt of the controller:
[Export(typeof(IMainWindowController))]
public class MainWindowController : IMainWindowController
{
private MainWindowViewModel _mainWindowViewModel;
public MainWindowViewModel MainWindowViewModel
{
get { return _mainWindowViewModel; }
set { _mainWindowViewModel = value; }
}
[ImportingConstructor]
public MainWindowController(MainWindowViewModel mainWindowViewModel)
{
_mainWindowViewModel = mainWindowViewModel;
}
public void Initialize()
{
_mainWindowViewModel.LabelContent = "stfu";
}
}
The view interface:
public interface IMainWindowView : IView
{
}
And the view itself:
[Export(typeof(IMainWindowView))]
public partial class MainWindow : Window, IMainWindowView
{
public MainWindow()
{
InitializeComponent();
}
}
<Window x:Class="MyCompany.Product.Redesign.Presentation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Label Content="Test" />
<Label Name="MyLabel" Content="{Binding Path=LabelContent, FallbackValue=Fallback}" />
</StackPanel>
</Window>
Are you sure, that the view that is displayed really is the instance with the ViewModel-Instance you are setting the property on?
First, make sure that you don't have a view set as the Application's StartupUri-Property in the App.xaml. Then make sure, that you call View.Show() through your ViewModel. You are then certain that you really set the property on the instance that is being displayed:
App.xaml
<Application <!-- note: no StartupUri Property -->
x:Name="App" x:Class="YourProject.Presentation.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ShutdownMode="OnMainWindowClose">
</Application>
MainViewController.cs (with method declaration in IMainViewController.cs)
public void Run()
{
_mainWindowViewModel.Show();
}
App.xaml.cs
_controller = mainExportProvider.GetExportedValue<IMainViewController>();
_controller.Initialize();
_controller.Run();
MainViewModel.cs (with method declaration in IMainViewModel.cs)
public void Show()
{
ViewCore.Show();
}
This should do the trick. Otherwise, you might be seeing a view instance that you don't have a reference to. Thus you are setting a property on a ViewModel whos view isn't being displayed.
I'm looking for a way to do a "Touch" command binding between axml and ViewModel, or some else like FocusChanged etc.
A simple "Click" command works fine like so:
local:MvxBind="{'Touch':{'Path':'CameraButtonCommand'}}" />
public IMvxCommand CameraButtonCommand
{
get
{
return new MvxRelayCommand(
() =>
{
RequestNavigate<AugRealityViewModel>(true);
})
;
}
}
However, I've tried other event types for the controll(in this case it's ImageButton) and they are not being processed. When I've checked the events listings in the View Class I see those:
public event EventHandler Click;
public event EventHandler<View.CreateContextMenuEventArgs> ContextMenuCreated;
public event EventHandler<View.FocusChangeEventArgs> FocusChange;
public event EventHandler<View.KeyEventArgs> KeyPress;
public event EventHandler<View.LongClickEventArgs> LongClick;
Only Click event has the general EventHandler attached to it, while other have genericed EventHandlers, and I'm wondering if that's the reason why it doesn't work.
I've also tried to attach a method to those events in the View class getting the proper control by FindViewById method and it works as expected this time around. But somehow I can't do it in the axml through Commands.
Also one more thing. The "Click" event is sending the "EventArgs" object as one of the parameters, and also the object reference. I can see that with ease if I do this behaviour in View Class, but when I do this by binding, I don't see those arguments when I'm processing the Command in ViewModel.
The framework can automatically bind any events which require EventHandler types. However, for any events which require a templated EventHandler (with custom EventArgs) then you are correct - you'll need to include a custom Binding.
The good news is that custom bindings are easy to write and to include.
For example, to bind:
public event EventHandler<View.LongClickEventArgs> LongClick;
you can include something like:
public class LongPressEventBinding
: MvxBaseAndroidTargetBinding
{
private readonly View _view;
private IMvxCommand _command;
public LongPressEventBinding(View view)
{
_view = view;
_view.LongClick += ViewOnLongClick;
}
private void ViewOnLongClick(object sender, View.LongClickEventArgs eventArgs)
{
if (_command != null)
{
_command.Execute();
}
}
public override void SetValue(object value)
{
_command = (IMvxCommand)value;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_view.Click -= ViewOnLongClick;
}
base.Dispose(isDisposing);
}
public override Type TargetType
{
get { return typeof(IMvxCommand); }
}
public override MvxBindingMode DefaultMode
{
get { return MvxBindingMode.OneWay; }
}
}
Which can be configured in setup using something like:
protected override void FillTargetFactories(IMvxTargetBindingFactoryRegistry registry)
{
base.FillTargetFactories(registry);
registry.RegisterFactory(new MvxCustomBindingFactory<View>("LongPress", view => new LongPressEventBinding(view)));
}
Note that you can't write a single class that binds to all the different event types - as the compiler requires you to include the correct Type for the EventArgs. However, you could fairly easily change public class LongClickEventBinding to something like public class CustomEventBinding<TViewType, TEventArgsType> if you wanted to.
With regards to what argument you should pass into the IMvxCommand Execute method, I guess this depends a bit on the method in question, and it also depends on whether you need the ViewModel to support multiple platforms, or whether it is just for Android.
In the StockTraderRI sample code the ViewModel is injected by MEF using a property:
[Export(typeof(IOrdersView))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class OrdersView : UserControl, IOrdersView
{
public OrdersView()
{
InitializeComponent();
}
[Import]
[SuppressMessage("Microsoft.Design", "CA1044:PropertiesShouldNotBeWriteOnly", Justification = "Needs to be a property to be composed by MEF")]
public IOrdersViewModel ViewModel
{
set { this.DataContext = value; }
}
}
What I wonder is: why not use an ImportingConstructor like this to inject the ViewModel:
[Export(typeof(IOrdersView))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public partial class OrdersView : UserControl, IOrdersView
{
[ImportingConstructor]
public OrdersView(IOrdersViewModel ViewModel)
{
InitializeComponent();
this.DataContext = ViewModel;
}
}
Is there a special feature, problem or reason I miss why the StockTraderRI sample does use a Property instead of a paramter to the ctor?
Because types partially defined in XAML don't play well with parametrized constructors. XAML is built on the "create a blank object and fill in the properties afterwards" paradigm.