MVVM and NavigationService - silverlight-4.0

One of the many benefits of implementing any pattern is to have a separation of concerns between the different layers in an application. In the case of Silverlight and MVVM it is my opinion that the NavigationService belongs to the UI.
If the NavigationService belongs to the UI then it should be used in the XAML code behind, but the commands happens on the ViewModel. Should I raise an event on the Command in the ViewModel and let the View handle the event and call the Navigation? That sounds a little absurd if all I'm doing is simply navigating to another page. Shouldn't I just handle the UI event directly and navigate from there?
View Control Event -> ViewModel Command -> Raise Event -> View
Handled Event -> Navigation
or
View Control Event -> View Handled Event -> Navigation

There are two documented approaches to this problem
Implementing the navigation using MVVM Light's messaging functionality This approach was put forward by Jesse Liberty in Part 3 his MVVM Ligtht soup to nuts series. His approach is to send a message from the command to the view indicating that a navigation operation should take place.
Implementing a ViewService that handles the navigationThis approach was Laurent Bugnion's response to Jesse's post. This implements a service that handles all navigation operations triggered by the view models.
Both approaches deal only with navigation in WP7 applications. However, they can be adapted to Silverligt applications too.
Jesse's approach is easier to use in SL as it does not require access to the root visual. However, the navigation code gets distributed in several places and requires code behind to do the actual navigation.
Laurent's approach requires access to the root visual - which is used for accessing the built-in navigation functionality. Getting access to this, as shown in Laurent's code, is no big deal in WP7 applications. In SL applications, however, it is slightly more complicated as there is no sourrounding frame. However, I alreay implented the pattern for SL in one of my projects using an attached property do do the required wiring - so although requires more work, it is usable for SL as well.
So concluding - although, Jesse's approach is easier to implement, personally I prefer Laurent's approach for it is cleaner architecture - there is no code behind required, and the functioality is encapsulated into a separate component and thus located at a single point.

A bit late to this question, but it is relevant and will hopefully be of benefit to someone. I had to create a SL4 application with MvvmLight and wanted to use a navigation service wrapper that was mock-able and could be injected into the ViewModel. I found a good starting point here: Laurent Bugnion's SL4 sample code samples from Mix11 which includes a navigation service demo: Deep Dive MVVM Mix11
Here are the essential parts for implementing a mock-able navigation service that can be used with Silverlight 4. The key issue is getting a reference to the main navigation frame to be used in the custom NavigationService class.
1) In MainPage.xaml, the navigation frame is given a unique name, for this example it will be ContentFrame:
<navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}"
Source="/Home" Navigated="ContentFrame_Navigated"
NavigationFailed="ContentFrame_NavigationFailed">
<!-- UriMappers here -->
</navigation:Frame>
2) In MainPage.xaml.cs, the navigation frame is exposed as a property:
public Frame NavigationFrame
{
get { return ContentFrame; }
}
3) The navigation service class implements the INavigationService interface and relies on the NavigationFrame property of MainPage.xaml.cs to get a reference to the navigation frame:
public interface INavigationService
{
event NavigatingCancelEventHandler Navigating;
void NavigateTo(Uri uri);
void GoBack();
}
public class NavigationService : INavigationService
{
private Frame _mainFrame;
public event NavigatingCancelEventHandler Navigating;
public void NavigateTo(Uri pageUri)
{
if (EnsureMainFrame())
_mainFrame.Navigate(pageUri);
}
public void GoBack()
{
if (EnsureMainFrame() && _mainFrame.CanGoBack)
_mainFrame.GoBack();
}
private bool EnsureMainFrame()
{
if (_mainFrame != null)
return true;
var mainPage = (Application.Current.RootVisual as MainPage);
if (mainPage != null)
{
// **** Here is the reference to the navigation frame exposed earlier in steps 1,2
_mainFrame = mainPage.NavigationFrame;
if (_mainFrame != null)
{
// Could be null if the app runs inside a design tool
_mainFrame.Navigating += (s, e) =>
{
if (Navigating != null)
{
Navigating(s, e);
}
};
return true;
}
}
return false;
}
}

Related

Xamarin.Forms Communication Between Two Pages Within Same App on Different Devices

Technologies, frameworks and devices I'm using:
Framework: Xamarin.Forms
IDE: Visual Studio 2022
Physical Device (smartphone): Zebra TC26 (Android 10)
Physical Device (smartwatch): Samsung Galaxy Watch4 (Android 11)
Problem definition
Currently I have a test Xamarin.Forms project that consists of two different UIs (XAML files):
User Interface 1: HomePage.XAML - This screen should be displayed on the smartphone
User Interface 2: WatchScreen.XAML - This screen should be displayed on the smartwatch
With code below I make sure HomePage.XAML is deployed to a smartphone and watchscreen is deployed to a smartwatch:
Page homePage = new NavigationPage(new HomePage());
// BuildVersionCodes.R is a reference to Android version 11 (mostly now used by Wear OS 3.x)
if (Build.VERSION.SdkInt == BuildVersionCodes.R)
{
// SM-R870 is a reference to the Samsung Galaxy Watch4
// Note: This is needed to ensure the UI is specific to the UI of a smartwatch
if (Build.Model == "SM-R870")
{
Page watchScreen = new NavigationPage(new WatchScreen());
MainPage = watchScreen;
}
}
else
{
MainPage = homePage;
}
Now I want to make these pages on different devices communicate with each other. HomePage.xaml exists within the main Xamarin.Forms project as well as WatchScreen.xaml.
The way I want them to communicate with each other is by sending a message or something. A Xamarin.Forms project also comes with a native project. In this native Xamarin.Android project I try to retrieve inside the MainActivity.cs the button that exists within the main project by using (in WatchScreen.xaml this button exists and in WatchScreen.xaml.cs I have a method that gives this button back).
Method in WatchScreen.xaml.cs that gives button back:
public Button GetSendButtonFromWearableUI() => btnSendMessage;
In MainActivity.cs I get this method by using:
Button button = (App.Current.MainPage.Navigation.NavigationStack.LastOrDefault() as WatchScreen)
.GetSendButtonFromWearableUI();
Whenever I click on the button by doing this:
button.Clicked += delegate
{
SendData();
};
Some data should be sent from MainActivity.cs and catched by HomePage.xaml and displayed on it. I tried several approaches but I didn't succeed in achieving what needs to happen.. Therefore, I'm wondering if you guys could help me out with this and would be much appreciated.
In the meantime I've been investigating this issue and came up with a solution. Follow steps below to get the same result. To make this solution work I've combined the Wearable Data Layer API from Google and MessagingCenter from Microsoft.
Also the example below shows only the communication from the smartwatch to the smartphone. In order to reverse processes you can put the send button on the HomePage instead of the smartwatch screen and make sure to subscribe to the correct messages.
One last note: keep in mind that code used below from Google is deprecated but it still works...
References used to make this work:
Syncing Data Between Wearable and Handheld Devices Using Xamarin in Android
Installed dependencies on the Xamarin.Android project within Xamarin.Forms project:
Xamarin.Android.Support.v4
Xamarin.GooglePlayServices.Base
Xamarin.GooglePlayServices.Wearable
MessageKeys.cs
This class is used to declare message keys that are being used to send and receive messages between devices.
public class MessageKeys
{
public const string Smartwatch = "Smartwatch";
public const string Smartphone = "Smartphone";
}
Xamarin.Forms (Base project) - App.xaml.cs
In the App.xaml.cs, as pointed out earlier, I'm making sure the wearable UI displays WatchScreen.xaml and any other devices display regular Android UI -> HomePage.xaml.
Xamarin.Forms (Base project) - WatchScreen.xaml.cs
Send message from Wearable device to Android smartphone.
private void btnSendMessage_Clicked(object sender, EventArgs e)
{
MessagingCenter.Send(Xamarin.Forms.Application.Current, MessageKeys.Smartwatch);
}
Xamarin.Forms (Base project) - HomePage.xaml.cs
public HomePage()
{
InitializeComponent();
MessagingCenter.Subscribe<Xamarin.Forms.Application>(Xamarin.Forms.Application.Current, MessageKeys.Smartphone, (sender) =>
{
DisplayAlert("Message", "Wearable message received!", "OK");
});
}
Xamarin.Forms (Native Android Project) - MainActivity.cs
Within MainActivity.cs I implement the following interfaces:
public class MainActivity : WearableActivity, DataClient.IOnDataChangedListener,
GoogleApiClient.IConnectionCallbacks, GoogleApiClient.IOnConnectionFailedListener
Variables:
private GoogleApiClient client;
const string syncPath = "/[project name]/[subdirectory for watch]";
Internal class 'MessageReceiver' for receiving broadcast messages:
[BroadcastReceiver]
public class MessageReciever : BroadcastReceiver
{
MainActivity main;
public MessageReciever() { }
public MessageReciever(MainActivity owner) { this.main = owner; }
public override void OnReceive(Context context, Intent intent)
{
main.ProcessMessage(intent);
}
}
Registering receiver (to receive through Wearable Data Layer API), creating Google Client and Subscribing to smartwatch message (to retrieve message through MessagingCenter)
protected override void OnCreate(Bundle bundle)
{
IntentFilter filter = new IntentFilter(Intent.ActionSend);
MessageReciever receiver = new MessageReciever(this);
LocalBroadcastManager.GetInstance(this).RegisterReceiver(receiver, filter);
client = new GoogleApiClient.Builder(this, this, this)
.AddApi(WearableClass.Api)
.Build();
MessagingCenter.Subscribe<Xamarin.Forms.Application>(Xamarin.Forms.Application.Current, MessageKeys.Smartwatch, (sender) =>
{
SendData();
});
}
ProcessMessage method: sends received message from wearable to smartphone
public void ProcessMessage(Intent intent)
{
// For now I'm not sending the payload...
string message = intent.GetStringExtra("WearMessage");
MessagingCenter.Send(Xamarin.Forms.Application.Current, MessageKeys.Smartphone);
}
SendData(), OnStart(), OnStop(), OnDataChanged (didn't do anything with this part, because this is to receive messages outside the project and I don't need it for now), OnConnected(), OnConnectionSuspended(), OnConnectionFailed():
See the reference to see what code has been used, since code is exactly the same... P.S.: one thing for SendData has been changed. If you want to keep sending data, remove 'client.Disconenct()' from finally after the try and catch block.
Xamarin.Forms (Native Android Project) - WearableService inherits from WearableListenerService:
WearableService is a new class and created within the native project. Also for this part see the reference, because it's the exact same code being used within my project.
To get an overall overview of what's happening, I've visualized this in the diagram below: (example shows how communication works from smartwatch to smartphone)
If you want to communicate from smartphone to smartwatch, you could do something like this:
That's it guys. Now you will receive messages within the same application using the Wearable Data Layer API and MessagingCenter. Instead of having separate projects, we just use separate UIs to make this happen...

MvvmCross and back button in Windows Phone app

I'm building a Windows Phone app (8.1 using WinRT) using MvvmCross. To navigate to a new view I using ShowViewModel(). But when I hit the back button on the phone the app is closing instead of navigating back to the first view. How can I do it I want to return to the first view when I hitting the back button?
I solved it to use a interface in my viewmodel with a backbutton event, then I wrote a client speific implementation of it. In the viewmodel I handle the event and called the close method in the my base class MvxViewModel. Read more about my solution on my blog, http://danielhindrikes.se/windows-phone/handle-windows-phone-back-button-pressed-when-using-mvvm/
Here's a simpler solution. Create a base type for all your WP pages that derives from MvxWindowsPage. Then, handle the back key there and route the proper information to your VM:
public abstract class MyBaseView : MvxWindowsPage {
public MyBaseView() {
this.InitializeComponent();
HardwareButtons.BackPressed += HardwareButtons_BackPressed;
}
void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e) {
if (Frame.CanGoBack) {
var vm = ViewModel as MyBaseViewModel;
if (vm != null) {
e.Handled = true;
vm.GoBackCommand.Execute(null);
}
}
}
}
Now, you also have to make sure that you have a base viewmodel which derives from MvxViewModel and from which you derive all your VMs. That base VM should have a GoBackCommand observable property, and executing that command should do a simple Close(this).
To see what's going on under the hood, see this related question: Windows Phone 8.1 Universal App terminates on navigating back from second page?
EDIT
Fixed declaration.

Metro c++ async programming and UI updating. My technique?

The problem: I'm crashing when I want to render my incoming data which was retrieved asynchronously.
The app starts and displays some dialog boxes using XAML. Once the user fills in their data and clicks the login button, the XAML class has in instance of a worker class that does the HTTP stuff for me (asynchronously using IXMLHTTPRequest2). When the app has successfully logged in to the web server, my .then() block fires and I make a callback to my main xaml class to do some rendering of the assets.
I am always getting crashes in the delegate though (the main XAML class), which leads me to believe that I cannot use this approach (pure virtual class and callbacks) to update my UI. I think I am inadvertently trying to do something illegal from an incorrect thread which is a byproduct of the async calls.
Is there a better or different way that I should be notifying the main XAML class that it is time for it to update it's UI? I am coming from an iOS world where I could use NotificationCenter.
Now, I saw that Microsoft has it's own Delegate type of thing here: http://msdn.microsoft.com/en-us/library/windows/apps/hh755798.aspx
Do you think that if I used this approach instead of my own callbacks that it would no longer crash?
Let me know if you need more clarification or what not.
Here is the jist of the code:
public interface class ISmileServiceEvents
{
public: // required methods
virtual void UpdateUI(bool isValid) abstract;
};
// In main XAML.cpp which inherits from an ISmileServiceEvents
void buttonClick(...){
_myUser->LoginAndGetAssets(txtEmail->Text, txtPass->Password);
}
void UpdateUI(String^ data) // implements ISmileServiceEvents
{
// This is where I would render my assets if I could.
// Cannot legally do much here. Always crashes.
// Follow the rest of the code to get here.
}
// In MyUser.cpp
void LoginAndGetAssets(String^ email, String^ password){
Uri^ uri = ref new URI(MY_SERVER + "login.json");
String^ inJSON = "some json input data here"; // serialized email and password with other data
// make the HTTP request to login, then notify XAML that it has data to render.
_myService->HTTPPostAsync(uri, json).then([](String^ outputJson){
String^ assets = MyParser::Parse(outputJSON);
// The Login has returned and we have our json output data
if(_delegate)
{
_delegate->UpdateUI(assets);
}
});
}
// In MyService.cpp
task<String^> MyService::HTTPPostAsync(Uri^ uri, String^ json)
{
return _httpRequest.PostAsync(uri,
json->Data(),
_cancellationTokenSource.get_token()).then([this](task<std::wstring> response)
{
try
{
if(_httpRequest.GetStatusCode() != 200) SM_LOG_WARNING("Status code=", _httpRequest.GetStatusCode());
String^ j = ref new String(response.get().c_str());
return j;
}
catch (Exception^ ex) .......;
return ref new String(L"");
}, task_continuation_context::use_current());
}
Edit: BTW, the error I get when I go to update the UI is:
"An invalid parameter was passed to a function that considers invalid parameters fatal."
In this case I am just trying to execute in my callback is
txtBox->Text = data;
It appears you are updating the UI thread from the wrong context. You can use task_continuation_context::use_arbitrary() to allow you to update the UI. See the "Controlling the Execution Thread" example in this document (the discussion of marshaling is at the bottom).
So, it turns out that when you have a continuation, if you don't specify a context after the lambda function, that it defaults to use_arbitrary(). This is in contradiction to what I learned in an MS video.
However by adding use_currrent() to all of the .then blocks that have anything to do with the GUI, my error goes away and everything is able to render properly.
My GUI calls a service which generates some tasks and then calls to an HTTP class that does asynchronous stuff too. Way back in the HTTP classes I use use_arbitrary() so that it can run on secondary threads. This works fine. Just be sure to use use_current() on anything that has to do with the GUI.
Now that you have my answer, if you look at the original code you will see that it already contains use_current(). This is true, but I left out a wrapping function for simplicity of the example. That is where I needed to add use_current().

How to make a propertysheetpage be a selection provider?

I've got a contributed command and a handler for it. The handler's execute event has to get the value for the property actually selected in the properties view and act on it, or to be disabled if no property selected.
I've tried:
1) Set the selection provider to something which provides selection from the property view. Something in this case is just PropertySheetViewer for my PropertySheetPage, but i can't set it as the selection provider because the PropertySheetPage's viewer is private and has no getter.
2) Overriding PropertySheetPage's createControl method: This method creates a Tree control for the PropertySheetViewer. A selection listener can be installed for that tree control, so maybe i can make my command handler implement SelectionListener... The solution would be somethin like:
In my editor:
public Object getAdapter(#SuppressWarnings("rawtypes") Class type) {
if (type == IPropertySheetPage.class) {
PropertySheetPage page = new PropertySheetPage() {
#Override
public void createControl(Composite parent) {
super.createControl(parent);
IHandler handler = someWayToGetMyCmdHandler();
((org.eclipse.swt.widgets.Tree) getControl())
.addSelectionListener(handler);
}
};
IPropertySheetEntry entry = new UndoablePropertySheetEntry(
getCommandStack());
page.setRootEntry(entry);
return page;
}
return super.getAdapter(type);
}
And my command handler implementing SelectionListener as i said... The problem with this approach is that i can't find a way to get a reference to my contributed command handler (someWayToGetMyCmdHandler() above).
Has anybody got any clue on this, or any other possible approach to the problem??
There's handleEntrySelection(ISelection selection) method in PropertySheetPage that you could override to be notified about selection changes in the viewer (although PropertySheetPage is #noextend).
The second part (updating the handler) is a bit more tricky than it would normally be. Commands/handlers get updated automatically when workbench selection changes (you just need to implement setEnabled(Object evaluationContext) AbstractHandler). But since PropertySheetPage is designed to change its input on global selection change, then you have to find some custom way to notify/update your handler.
As I understand, it is currently not possible to extend the platform command event handling mechanism with custom variables, so you just need to directly look up your handler using IHandlerService of the workbench.

Simple example of DispatcherHelper

I'm trying to figure out how can I use DispatcherHelperftom MVVM light toolkit in SL, but I can't find any example.
From home page of this framework I know that
DispatcherHelper class, a lightweight class helping you to create
multithreaded applications.
But I don't know how to use it.
How and for what I can use it?
You only need the DispatcherHelper when yo want to make changes to components on your UI thread, from code that runs on a different thread. E.g. in an Silverlight application you call a web service to retrieve some data asynchroneously, and now want to inform the Ui that the data is present via a OnNotifyPropertyChanged event.
First you have to initialize the DispatcherHelper. In Silverlight you do this in Application_Startup:
//initialize Dispatch helper
private void Application_Startup( object sender, StartupEventArgs e) {
RootVisual = new MainPage();
DispatcherHelper.Initialize();
}
In WPF the initialization is done in the static constructor of you App class:
static App() {
DispatcherHelper.Initialize();
}
Then in your event, handling the completion of your asnc call, use the following code to call RaisePropertyChanged on the UI thread:
DispatcherHelper.CheckBeginInvokeOnUI(
() => RaisePropertyChanged(PowerStatePropertyName)
);
DispatcherHelper.BeginInvokeOnUl expects an Action so you can use any code in here just use
DispatcherHelper.CheckBeginInvokeOnUI(
() => { /* complex code goes in here */ }
);
to do more complex tasks.