Binding a button action to a Child ViewModel with Caliburn.Micro - c#

This should be easy, but its not working for me. I have a view called MainWindowView that contains a view called ChildView. The MainWindowView has a corresponding ViewModel called MainWindowViewModel and the ChildView has a ViewModel called ChildViewModel:
MainWindowView:
<Grid>
<views:ChildView x:Name="ChildView"/>
</Grid>
MainWindowViewModel:
public MainWindowViewModel()
{
ChildView = new ChildViewModel();
}
ChildView:
<Grid>
<Button Content="Edit" x:Name="Edit"/>
</Grid>
ChildViewModel:
public class ChildViewModel
{
public void Edit()
{
}
public ChildViewModel()
{
}
EDIT:
AppBootstrapper:
public class AppBootstrapper : BootstrapperBase
{
private SimpleContainer container;
public AppBootstrapper()
{
Initialize();
}
protected override void Configure()
{
container = new SimpleContainer();
container.Instance(container);
container
.Singleton<IWindowManager, WindowManager>()
.Singleton<IEventAggregator, EventAggregator>();
container.PerRequest<MainWindowViewModel>();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<MainWindowViewModel>();
}
protected override object GetInstance(Type service, string key)
{
return container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
container.BuildUp(instance);
}
protected override void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
e.Handled = true;
MessageBox.Show(e.Exception.Message, "An error as occurred", MessageBoxButton.OK);
}
}
and I might add that the views are in a root level Views folder and the view models are in a root level ViewModels folder. Why won't the Edit command fire?

well I am gonna go our on a limb and say that from my experience with CM and say that from what you have a shown there is probably a binding error. What is supposed to happen? Keep in mind CM is not view first out of the box with WPF. Can you show us your bootstrapper?

I was able to fix this by changing the MainWindowView to
<Grid>
<ContentControl x:Name="ChildView" />
</Grid>
Somehow Caliburn.Micro was not able to bind the ChildView to its ViewModel properly. I was confused because the ChildViewModel constructor was being called. I guess that Caliburn.Micro knows how to bind the ContentControl, but not my custom view.

Related

Xamarin.iOS AddObserver not firing for change event for udpate to UIView.Window

I'm trying to implement a lifecycle effect in Xamarin.Forms, but am having trouble for the iOS version. For some reason, I can't seem to observe the window changing notification event. Below is my code:
public class CustomLifeCycleEffectRouter : PlatformEffect
{
private const NSKeyValueObservingOptions ObservingOptions = NSKeyValueObservingOptions.Initial | NSKeyValueObservingOptions.New;
UIView? _nativeView;
CustomLifeCycleEffect? _lifeCycleEffect;
IDisposable _isLoadedObserverDisposable;
protected override void OnAttached()
{
_lifeCycleEffect = Element.Effects.OfType<CustomLifeCycleEffect>().FirstOrDefault() ?? throw new ArgumentNullException($"The effect {nameof(CustomLifeCycleEffect)} can't be null.");
_nativeView = Control ?? Container;
_isLoadedObserverDisposable = _nativeView?.AddObserver("window", ObservingOptions, isWindowAttachedObserver);
}
protected override void OnDetached()
{
_lifeCycleEffect?.RaiseUnloadedEvent(Element);
_isLoadedObserverDisposable.Dispose();
}
private void isWindowAttachedObserver(NSObservedChange nsObservedChange)
{
if (_nativeView.Window != null)
_lifeCycleEffect?.RaiseLoadedEvent(Element);
else
_lifeCycleEffect?.RaiseUnloadedEvent(Element);
}
}
I am well aware that the Xamarin.Community Toolkit has a similar effect, but it fires the event to early; I need it to fire when I can navigate up the hiearchy to the root parent. Can anybody see a problem?
Edit
I've created a small sample to replicate my behaviours and issues. It can be viewed here:
https://github.com/sonic1015/LifeCycleEffectTesting
The goal is to only have the following messages in the debug output:
$"{elementName} is already a page."
$"{elementName} is a child of {pageName}."
and NOT these ones:
$"{elementName} does not have a parent ???."
$"How can {elementName} be loaded and not have a parent in hierarchy ???."
$"WTF??? we never loaded {elementName}."
These messages can be found in the "ViewExtensions" class, and I've the goal is to have every user-created view fire off good messages.
One thing I've noticed:
I also included a variant of the Xamarin Community Toolkit version of the router effect in the platform project, and it actually works, with the exception that it seems if any views are templated, it will fire "loaded" when it does not yet have a parent. I think this is why it originally didn't work for me, so if I could figure out a way to work that little edge case out, I could use that version of the routing effect.
1.Create a ViewLifecycleEffect class that implements RoutingEffect in the shared project like below:
public class ViewLifecycleEffect : RoutingEffect
{
public const string EffectGroupName = "XFLifecycle";
public const string EffectName = "LifecycleEffect";
public event EventHandler<EventArgs> Loaded;
public event EventHandler<EventArgs> Unloaded;
public ViewLifecycleEffect() : base($"{EffectGroupName}.{EffectName}") { }
public void RaiseLoaded(Element element) => Loaded?.Invoke(element, EventArgs.Empty);
public void RaiseUnloaded(Element element) => Unloaded?.Invoke(element, EventArgs.Empty);
}
2.In Mainpage.xmal:
<StackLayout x:Name="MainContainer" Margin="20" VerticalOptions="Center" HorizontalOptions="Center">
<Button Text="CLICK TO REMOVE" Clicked="Button_OnClicked" HorizontalOptions="Center" VerticalOptions="Center">
<Button.Effects>
<effects:ViewLifecycleEffect Loaded="ViewLifecycleEffect_OnLoaded" Unloaded="ViewLifecycleEffect_OnUnloaded"/>
</Button.Effects>
</Button>
</StackLayout>
Code-behind:
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void Button_OnClicked(object sender, EventArgs e)
{
MainContainer.Children.Clear();
}
private void ViewLifecycleEffect_OnLoaded(object sender, EventArgs e)
{
DisplayAlert("LOADED", "Button was added", "OK");
}
private void ViewLifecycleEffect_OnUnloaded(object sender, EventArgs e)
{
DisplayAlert("UNLOADED", "Button was removed", "OK");
}
}
3.Create a IosLifecycleEffect in the iOS project.
[assembly:ResolutionGroupName(ViewLifecycleEffect.EffectGroupName)]
[assembly:ExportEffect(typeof(IosLifecycleEffect), ViewLifecycleEffect.EffectName)]
namespace XFLifecycle.iOS.Effects
{
public class IosLifecycleEffect : PlatformEffect
{
private const NSKeyValueObservingOptions ObservingOptions = NSKeyValueObservingOptions.Initial | NSKeyValueObservingOptions.OldNew | NSKeyValueObservingOptions.Prior;
private ViewLifecycleEffect _viewLifecycleEffect;
private IDisposable _isLoadedObserverDisposable;
protected override void OnAttached()
{
_viewLifecycleEffect = Element.Effects.OfType<ViewLifecycleEffect>().FirstOrDefault();
UIView nativeView = Control ?? Container;
_isLoadedObserverDisposable = nativeView?.AddObserver("superview", ObservingOptions, IsViewLoadedObserver);
}
protected override void OnDetached()
{
_viewLifecycleEffect.RaiseUnloaded(Element);
_isLoadedObserverDisposable.Dispose();
}
private void IsViewLoadedObserver(NSObservedChange nsObservedChange)
{
if (!nsObservedChange.NewValue.Equals(NSNull.Null))
_viewLifecycleEffect?.RaiseLoaded(Element);
else if (!nsObservedChange.OldValue.Equals(NSNull.Null))
_viewLifecycleEffect?.RaiseUnloaded(Element);
}
}
}

App crashes when Navigation from pages via back button

I am developing Windows Phone 8.1 app with MVVM.
I have base view model class which contains Navigation Service as below:
public abstract class BaseViewModel : INotifyPropertyChanged
{
protected readonly INavigationService NavigationService;
//....
}
There is my navigation service class:
public class NavigationService : INavigationService
{
public void Navigate(Type destinationPage)
{
((Frame)Window.Current.Content).Navigate(destinationPage);
}
public void Navigate(Type desitnationPage, object parameter)
{
((Frame)Window.Current.Content).Navigate(desitnationPage, parameter);
}
public void GoBack()
{
((Frame)Window.Current.Content).GoBack();
}
}
Everything is working fine when I am binding commands from XAML. There is problem when I want to override BackButton. I have also created base page model which also contains NavigationService. Each page has an overridde pf BackPressed as below:
public class BasePage : Page
{
protected INavigationService NavigationService => ComponentManager.GetInstance<INavigationService>();
public BasePage()
{
//...
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
}
protected virtual void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)
{
//Frame.Navigate(typeof(MainPage));
(this.DataContext as BaseViewModel)?.Back.Execute(sender);
}
}
As you see in HardwareButtons_BackPressed method I've tried to make it in to ways but none is workings. Every time I press back button application crashes without any error.
I don't think the app is crashing, it's just exiting because that is the default behaviour of the back button.
What you need to do is flag that you've handled the back button by adding this line of code in your BackPressed event handler:
e.Handled = true;

Pass a method as parameter to an XAML object

I'm currently working with a CustomObject that needs a CustomObjectRenderer for each platform.
I would like to pass a method as parameter to this object, from the XAML side, so I would be able to use this callback, from my renderer.
<control:CustomObject Callback="CallbackFunction"/>
The CallbackFunction(object param) is then declared in the MainPage.xaml.cs of the PCL part.
public partial class MainPage : ContentPage
{
public MainPage()
{
base.BindingContext = this;
}
public void CallbackFunction(object param)
{
Debug.WriteLine((object as Element).Name);
}
}
So, if I'm understanding well, my CustomObject have to be like that:
public CustomObject : Object
{
public Action<object> Callback { get; set; }
}
But I have an error about XAML parsing.. I don't get why this error is thrown..
At the end, what I want to do, it's to call this method from the renderer, and then handle things, do actions from the MainPage.xaml.cs, from the PCL part.
public class CustomObjectRenderer : ObjectRenderer
{
NativeObject nativeObject;
CustomObject customObject;
protected override void OnElementChanged(ElementChangedEventArgs<CustomObject> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
customObject = e.NewElement as CustomObject;
nativeObject = Control as NativeObject;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
}
// Etc etc ....
private void METHOD_CALLED_BY_EVENT(object o)
{
// This method get call by the renderer event and then, I want to call
// the method CallbackFunction(object); and do actions.
customObject.Callback(o as OBJECT_PARAM);
}
}
Ok, it's a bit hard for me to explain my problem to you, so if you don't understand something, let me know.
You can achieve this by using events.
MyView
public class MyView : View
{
public event EventHandler<string> MyEvent;
public void RaiseEvent(string parameter)
{
MyEvent?.Invoke(this, parameter);
}
}
Page.xaml
<local:MyView MyEvent="MyView_OnMyEvent"></local:MyView>
Page.xaml.cs
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
private void MyView_OnMyEvent(object sender, string e)
{
Debug.WriteLine(e);
}
}
Renderer
public class MyViewRenderer : ViewRenderer<MyView, SomeNativeView>
{
private void METHOD_CALLED_BY_EVENT(string param)
{
Element.RaiseEvent(param);
}
}
After lot of tried, which didn't work, I had an idea, I tried and it works as I wanted by asking my question.
First, create your custom object !
CustomView
public class CustomView : View
{
public static readonly BindableProperty MainPageCallbackProperty =
BindableProperty.Create(nameof(MainPageCallback), typeof(Action<object>), typeof(CustomMap), null);
public Action<object> MainPageCallback
{
get { return (Action<object>)GetValue(MainPageCallbackProperty); }
set { SetValue(MainPageCallbackProperty, value); }
}
}
We so use Action which is a container for a method/callback. But in my example, we will use Action<object>. Why? Because it will allows us to have an object has paramter to our callback, so we will be able to bring data back from the renderer.
Then, create a page called MainPage.xaml by example. In the XAML part of this new page, add the following code:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:control="clr-namespace:Project.CustomControl;assembly=Project"
x:Class="Project.Page.MainPage">
<ContentPage.Content>
<control:CustomView MainPageCallback="{Binding MainPageCallbackAction}"
VerticalOptions="Fill" HorizontalOptions="Fill"/>
</ContentPage.Content>
</ContentPage>
About this XAML, two parts interest us.
XAML 'References'
xmlns:control="clr-namespace:Project.CustomControl;assembly=Project"
By these this xmlns, you can access your custom control.
Content of the page
<ContentPage.Content>
<control:CustomView MainPageCallback="{Binding MainPageCallbackAction}"
VerticalOptions="Fill" HorizontalOptions="Fill"/>
</ContentPage.Content>
Now, we bind the MainPageCallback of our object to the MainPageCallbackAction, declared in the C# side.
After that, our MainPage.xaml.cs would seems like that:
public partial class MainPage : ContentPage
{
public Action<object> MainPageCallbackAction { get; set; }
public MainPage()
{
base.BindingContext = this;
MainPageCallbackAction = MainPageCallbackMethod;
InitializeComponent();
}
private void MainPageCallbackMethod(object param)
{
Device.BeginInvokeOnMainThread(() =>
{
Debug.WriteLine("Welcome to the Callback :)");
Debug.WriteLine("Emixam23 - Example");
});
}
}
Now, the last thing to look at is the CustomViewRenderer !
public class CustomViewRenderer : ViewRenderer<CustomView, NativeView>
{
CustomView customView;
NativeView nativeView;
protected override void OnElementChanged(ElementChangedEventArgs<CustomView> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
customView = e.NewElement as CustomView;
nativeView = Control as NativeView;
NativeView.CLicked += METHOD_CALLED_BY_EVENT;
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
}
private void METHOD_CALLED_BY_EVENT(object sender, EventArgs ea)
{
customView.MainPageCallback(ea.something.information);
}
}
And then, take a look at the output, you'll be able to see the following:
Welcome to the Callback :)
Emixam23 - Example
I hope this answer is clear and helps you !

How to Navigate to a user control on load?

I want to choose which user control to load but my MainWindowView is not even loaded yet so the region manager does not know any regions, How can I achieve this?
my bootstrapper looks like this:
protected override DependencyObject CreateShell()
{
return this.Container.Resolve<MainWindowView>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
this.Container.RegisterTypeForNavigation<WorkTypeSelectionView>();
}
and my viewmodel:
public MainWindowViewModel(IEventAggregator eventAggregator, IRegionManager regionManager)
{
this.eventAggregator = eventAggregator;
this.regionManager = regionManager;
this.AuthenticateUser();
if (this.LoggedUser.AvailableWorkTypes.Count > 1)
{
this.Navigate(nameof(WorkTypeSelectionView));
}
}
private void Navigate(string obj)
{
this.regionManager.RequestNavigate(DefaultContentRegion, obj);
}
thanks in advance!
EDIT:
Guess i was asking the wrong question, found this https://stackoverflow.com/a/7887936/171136 still want to explore other options. Thanks!
You can use View Discovery with regionManager.RegisterViewWithRegion("RegionName", typeof(View));. When the region is created, it will automatically inject the view.

PRISM navigate to another View

I have a MainWindow which only contains the region for displaying other Views:
<ContentControl Grid.Row="1" prism:RegionManager.RegionName="ContentRegion"/>
What I am trying to do, is to immediately when my MainWindowViewModel loads, to navigate to MainPageViewModel.
I have tried to implement interface INavigationAware such as following:
public void OnNavigatedTo(NavigationContext navigationContext)
{
_regionManager.RequestNavigate("ContentRegion", App.Experiences.DetailPage.ToString());
}
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
_regionManager.RequestNavigate("ContentRegion", App.Experiences.DetailPage.ToString());
}
But even when I set my breakpoints over these, they are never executed.
What am I doing wrong?
EDIT
Maybe I need to change my Bootstrapper logic? Here is how it looks like:
public class Bootstrapper: UnityBootstrapper
{
protected override System.Windows.DependencyObject CreateShell()
{
return Container.TryResolve<MainWindow>();
}
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
}
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType(typeof(IDataRepository), typeof(DataRepository), null,new Microsoft.Practices.Unity.ContainerControlledLifetimeManager());
Container.RegisterTypeForNavigation<MainPage>(App.Experiences.MainPage.ToString());
Container.RegisterTypeForNavigation<DetailPage>(App.Experiences.DetailPage.ToString());
}
}
I had to edit my Bootstrapper logic such as following:
protected override void InitializeShell()
{
Application.Current.MainWindow.Show();
Prism.Regions.IRegionManager newRegion = Container.TryResolve<Prism.Regions.IRegionManager>();
newRegion.RequestNavigate("ContentRegion", App.Experiences.MainPage.ToString());
}

Categories

Resources