I am looking for a possibility to start my own Windows App after some content is shared with it through the charm bar.
I have found this MS example https://code.msdn.microsoft.com/windowsapps/Sharing-Content-Target-App-e2689782/
But after the share button is clicked the app closes.
I have tryed it this code at the button click method:
var rootFrame = new Frame();
rootFrame.Navigate(typeof(DefaultPage));
Window.Current.Content = rootFrame;
Window.Current.Activate();
But this has no effect. Also I have tryed to use Application.Start() but the parameter should be a Callback and I don't understand which one.
-------Edit:
I want the following behavior.
open IE (already done)
open Charm Bar and click on Share (already done)
select my App (already done)
My App show a Sharing page with information and a share button (already done)
after the share button at my app is clicked open Mainpage (at the MS example the Default page)
So I can't find a solution for the last step. Switching from the Sharing page of my app to the Main Page of my app.
Edit End-------
Every thing I want is to start my App after some content is shared with it.
I hope somebody can help me. Or is this not possible?
----Edit 2:
App.xaml.cs:
using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
// The Blank Application template is documented at http://go.microsoft.com/fwlink/?LinkId=234227
namespace AppName
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
{
this.DebugSettings.EnableFrameRateCounter = true;
}
#endif
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
// Set the default language
rootFrame.Language = Windows.Globalization.ApplicationLanguages.Languages[0];
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
// Ensure the current window is active
Window.Current.Activate();
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
deferral.Complete();
}
/// <summary>
/// Invoked when the application is activated as the target of a sharing operation.
/// </summary>
/// <param name="e">Details about the activation request.</param>
protected override void OnShareTargetActivated(Windows.ApplicationModel.Activation.ShareTargetActivatedEventArgs e)
{
var shareTargetPage = new AppName.ShareTargetPage();
shareTargetPage.Activate(e);
Window.Current.Activate();
}
protected override void OnActivated(IActivatedEventArgs args)
{
base.OnActivated(args);
}
}
}
Mainpage.xaml.cs:
using System;
using Windows.UI.Xaml.Controls;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace AppName
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
public void ReceiveUri(Uri sharedWebLink)
{
tbMessages.Text = sharedWebLink.ToString();
}
protected override void OnNavigatedTo(Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
// It is possible to get in this method after the Share button at the
// sharetargetpage is clicked but at this point I don't know how to
// activate the app
//base.OnNavigatedTo(e);
}
}
}
Sharepargetpage.xaml.cs:
using Windows.UI.Core;
using AppName.Common;
using System;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
// The Share Target Contract item template is documented at http://go.microsoft.com/fwlink/?LinkId=234241
namespace AppName
{
/// <summary>
/// This page allows other applications to share content through this application.
/// </summary>
public sealed partial class ShareTargetPage : Page
{
private Uri sharedWebLink;
/// <summary>
/// Provides a channel to communicate with Windows about the sharing operation.
/// </summary>
private Windows.ApplicationModel.DataTransfer.ShareTarget.ShareOperation _shareOperation;
private ObservableDictionary defaultViewModel = new ObservableDictionary();
/// <summary>
/// This can be changed to a strongly typed view model.
/// </summary>
public ObservableDictionary DefaultViewModel
{
get { return this.defaultViewModel; }
}
public ShareTargetPage()
{
this.InitializeComponent();
}
/// <summary>
/// Invoked when another application wants to share content through this application.
/// </summary>
/// <param name="e">Activation data used to coordinate the process with Windows.</param>
public async void Activate(ShareTargetActivatedEventArgs e)
{
this._shareOperation = e.ShareOperation;
// Communicate metadata about the shared content through the view model
var shareProperties = this._shareOperation.Data.Properties;
var thumbnailImage = new BitmapImage();
this.DefaultViewModel["Title"] = shareProperties.Title;
this.DefaultViewModel["Description"] = shareProperties.Description;
this.DefaultViewModel["Image"] = thumbnailImage;
this.DefaultViewModel["Sharing"] = false;
this.DefaultViewModel["ShowImage"] = false;
this.DefaultViewModel["Comment"] = String.Empty;
this.DefaultViewModel["Placeholder"] = "Add a comment";
this.DefaultViewModel["SupportsComment"] = true;
Window.Current.Content = this;
Window.Current.Activate();
try
{
this.sharedWebLink = await this._shareOperation.Data.GetWebLinkAsync();
this.DefaultViewModel["URL"] = this.sharedWebLink.ToString();
}
catch (Exception ex)
{
NotifyUserBackgroundThread("Failed GetWebLinkAsync - " + ex.Message, NotifyType.ErrorMessage);
}
// Update the shared content's thumbnail image in the background
if (shareProperties.Thumbnail != null)
{
var stream = await shareProperties.Thumbnail.OpenReadAsync();
thumbnailImage.SetSource(stream);
this.DefaultViewModel["ShowImage"] = true;
}
}
/// <summary>
/// Invoked when the user clicks the Share button.
/// </summary>
/// <param name="sender">Instance of Button used to initiate sharing.</param>
/// <param name="e">Event data describing how the button was clicked.</param>
private void ShareButton_Click(object sender, RoutedEventArgs e)
{
this.DefaultViewModel["Sharing"] = true;
this._shareOperation.ReportStarted();
// TODO: Perform work appropriate to your sharing scenario using
// this._shareOperation.Data, typically with additional information captured
// through custom user interface elements added to this page such as
// this.DefaultViewModel["Comment"]
this._shareOperation.ReportCompleted();
// TRY1: Navigate to MainPage
//Frame rootFrame = Window.Current.Content as Frame;
//if(rootFrame == null)
//{
// rootFrame = new Frame();
// Window.Current.Content = rootFrame;
//}
//if(rootFrame.Content == null)
//{
// rootFrame.Navigate(typeof(MainPage));
//}
// TRY2: Navigate to MainPage
//var p = rootFrame.Content as MainPage;
//p.ReceiveUri(sharedWebLink);
//Window.Current.Activate();
var rootFrame = new Frame();
rootFrame.Navigate(typeof(MainPage));
Window.Current.Content = rootFrame;
Window.Current.Activate();
// TRY3: Start the App
// Application.Start();
// App.Start();
// but I don't know which callback method should be the param of start
}
async private void NotifyUserBackgroundThread(string message, NotifyType type)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
NotifyUser(message, type);
});
}
private void NotifyUser(string strMessage, NotifyType type)
{
switch (type)
{
// Use the status message style.
case NotifyType.StatusMessage:
StatusBlock.Style = Resources["StatusStyle"] as Style;
break;
// Use the error message style.
case NotifyType.ErrorMessage:
StatusBlock.Style = Resources["ErrorStyle"] as Style;
break;
}
StatusBlock.Text = strMessage;
}
public enum NotifyType
{
StatusMessage,
ErrorMessage
};
}
}
Edit 2 End ---------
To enable your app's behavior as Share Targeted app, you have to override OnShareTargetActivated(ShareTargetActivatedEventArgs args) method in App class.
protected override void OnShareTargetActivated(ShareTargetActivatedEventArgs args)
{
var rootFrame = new Frame();
// TODO: Load content in frame
Window.Current.Content = rootFrame;
Window.Current.Activate();
}
EDIT
From your current implementation you are going to setShareTargetPage inside windows and later you cast Windows.Current.Content to Frame that is always null. So, you can't do this way. My recommendation is to do it this way.
Change OnShareTargetActivated method inside App.xaml.cs to this
protected override void OnShareTargetActivated(ShareTargetActivatedEventArgs args)
{
var rootFrame = new Frame();
rootFrame.Navigate(typeof(ShareTargetPage), args);
Window.Current.Content = rootFrame;
Window.Current.Activate();
}
Remove this from Activate method in ShareTargetPage.xaml.cs
Window.Current.Content = this;
Window.Current.Activate();
Overrides OnNavigatedTo in ShareTargetPage class
protected override void OnNavigatedTo(NavigationEventArgs e)
{
// Calling Activate method here
Activate((ShareTargetActivatedEventArgs)e.Parameter);
}
And then to navigate to another page you can simply Calls Frame.Navigation something like that inside ShareTargetPage
this.Frame.Navigate(typeof(MainPage));
EDIT 2
If you want to navigate to another page on share button click then remove this line of code
this._shareOperation.ReportCompleted();
This calling above method informs the OS that your app has finished sharing request and is now safely terminated. That's why your app terminates without navigation to another page
What you are trying to do goes against the Windows Store guidelines, and your app would be rejected if you managed to achieve it.
See the guidelines here: http://msdn.microsoft.com/en-us/library/windows/apps/hh465251.aspx
Your share target app must pop up only as a share target and must be dismissed with any user interaction outside the sharing panel.
You could probably get around this by adding a button in the share panel 'launch full app', and then create a custom URI that launches your app. But that is probably not advisable.
Related
I'm developing a cellphone app for iOS and Android platforms using Xamarin and MvvmCross. In the Android version, I can receive push notifications while the app is in foreground and when the app is closed and navigate to the desired view model in these two cases.
I'm having problems to navigate to a specific view model from the splash screen when the app is in background and is resumed because user tapped in a push notification.
I could persist a flag and then check in every view model that is resumed if this flag is set and perform the navigation, but I don't really like the solution and would like to use events as I do when the app is in foreground. The problem is that when the app is in background and resumed from push notification, my events aren't still assigned to its handlers.
This is my code. SplashScreen:
public class SplashScreen : MvxSplashScreenAppCompatActivity
{
private bool _setPushNotificationHint;
private Dictionary<string, string> _pushNotificationData;
public SplashScreen() : base(Resource.Layout.SplashScreenLayout)
{
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
if (Intent.Extras != null)
{
_pushNotificationData = new Dictionary<string, string>();
foreach (var key in Intent.Extras.KeySet())
{
var value = Intent.Extras.Get(key);
if(value != null)
{
_pushNotificationData.Add(key, value.ToString());
}
}
_setPushNotificationHint = true;
}
//avoid showing splash screen when resuming app pressing app icon or when tapping on push notification
if (!IsTaskRoot)
{
//deliver notification only when app is started and is in background. When it's closed we'll use GetAppStartHint
if (_setPushNotificationHint)
{
PushNotificationManager.OnMessageReceivedAppInBackground(this, _pushNotificationData);
}
Finish();
return;
}
//just initialize essentials when first start of the app, not when splash screen is shown
Xamarin.Essentials.Platform.Init(this, bundle);
}
Here is where i get the push notification information and trigger my PushNotificationManager, that should fire the event. Here's an extract of it:
public class PushNotificationManager : IPushNotification
{
public const string controlIdKey = "controlId";
private static PushNotificationDataEventHandler _onNotificationReceived;
public event PushNotificationDataEventHandler OnNotificationReceived
{
add
{
_onNotificationReceived += value;
}
remove
{
_onNotificationReceived -= value;
}
}
private static PushNotificationDataEventHandler _onNotificationReceivedWhileForeground;
public event PushNotificationDataEventHandler OnNotificonReceivedWhileForegroundationReceived
{
add
{
_onNotificationReceivedWhileForeground += value;
}
remove
{
_onNotificationReceivedWhileForeground -= value;
}
}
/// <summary>
/// Handles an incoming push notification in Android when app is in Foreground.
/// </summary>
/// <param name="remoteMessage">User info.</param>
public static void OnMessageReceived(RemoteMessage remoteMessage)
{
var notification = CreateNotificationFromRemoteMessage(remoteMessage);
_onNotificationReceivedWhileForeground?.Invoke(CrossPushNotification.Instance, new PushNotificationDataEventArgs(notification));
}
/// <summary>
/// When app is in background in Android, we get to the splashscreen, who will call this method to publish a NotificationMessage to the interested ViewModels
/// </summary>
/// <param name="sender">Sender.</param>
/// <param name="parameters">Parameters.</param>
public static void OnMessageReceivedAppInBackground(object sender, Dictionary<string, string> parameters)
{
var notification = CreateNotificationFromIntentExtras(parameters);
//we need to publish a message instead of rising an event because this is triggered from the splash screen and the view model
//Mvx.IoCProvider.Resolve<IMvxMessenger>().Publish(notificationMessage);
//_onNotificationReceived?.Invoke(CrossPushNotification.Instance, new PushNotificationDataEventArgs(notification));
//todo find a way to properly notify the current view to navigate to the device details view model
}
/// <summary>
/// In Android plataform, we create the notification from the parameters received in the splashscreen as intent extras
/// </summary>
/// <returns>The notification from intent extras.</returns>
/// <param name="parameters">Parameters.</param>
private static Notification CreateNotificationFromIntentExtras(Dictionary<string, string> parameters)
{
parameters.TryGetValue(controlIdKey, out var controlId);
return new Notification
{
ControlId = controlId
};
}
Then in the BaseViewModel.cs I attach the handler of the events that should be fired from the PushNotificationManager:
public override void ViewAppeared()
{
base.ViewAppeared();
//_token = Mvx.IoCProvider.Resolve<IMvxMessenger>().Subscribe<NotificationMessage>(OnNotificaitonMessage);
CrossPushNotification.Instance.OnNotificationReceived += NotificationReceived;
CrossPushNotification.Instance.OnNotificonReceivedWhileForegroundationReceived += NotificationInForegroundReceived;
}
public override void ViewDisappeared()
{
base.ViewDisappeared();
//Mvx.IoCProvider.Resolve<IMvxMessenger>().Unsubscribe<NotificationMessage>(_token);
CrossPushNotification.Instance.OnNotificationReceived -= NotificationReceived;
CrossPushNotification.Instance.OnNotificonReceivedWhileForegroundationReceived -= NotificationInForegroundReceived;
}
public override void ViewDestroy(bool viewFinishing = true)
{
//Mvx.IoCProvider.Resolve<IMvxMessenger>().Unsubscribe<NotificationMessage>(_token);
CrossPushNotification.Instance.OnNotificationReceived -= NotificationReceived;
CrossPushNotification.Instance.OnNotificonReceivedWhileForegroundationReceived -= NotificationInForegroundReceived;
base.ViewDestroy(viewFinishing);
}
But these event handlers are null when app comes from background because the view didn't appear yet. I can't attach the handler of the events in the constructor because then I can't remove it when the view is disappeared. Only the current view model should handle the event and navigate to my desired viewmodel.
I've also tried the MvxMessenger plugin without success.
So my question is, how can I properly notify my view models that a push notification came when app was in background?
I'm developing a Xamarin.Forms Android-App where on a ContentPage a CustomRenderer is being used to display a SearchView in the Header of the Page. The CustomRenderer for the "SearchPage" class looks like the following:
/// <summary>
/// The search page renderer.
/// </summary>
public class SearchPageRenderer : PageRenderer
{
/// <summary>
/// Gets or sets the search view.
/// </summary>
private SearchView searchView;
/// <summary>
/// Gets or sets the toolbar.
/// </summary>
private Toolbar toolbar;
/// <summary>
/// Reaction on the disposing of the page.
/// </summary>
/// <param name="disposing">A value indicating whether disposing.</param>
protected override void Dispose(bool disposing)
{
if (this.searchView != null)
{
this.searchView.QueryTextChange -= this.OnQueryTextChangeSearchView;
}
this.toolbar?.Menu?.RemoveItem(Resource.Menu.mainmenu);
base.Dispose(disposing);
}
/// <summary>
/// Reaction on the element changed event.
/// </summary>
/// <param name="e">The event argument.</param>
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e?.NewElement == null || e.OldElement != null)
{
return;
}
this.AddSearchToToolBar();
}
/// <summary>
/// Adds a search item to the toolbar.
/// </summary>
private void AddSearchToToolBar()
{
this.toolbar = (CrossCurrentActivity.Current?.Activity as MainActivity)?.FindViewById<Toolbar>(Resource.Id.toolbar);
if (this.toolbar != null)
{
this.toolbar.Title = this.Element.Title;
this.toolbar.InflateMenu(Resource.Menu.mainmenu);
this.searchView = this.toolbar.Menu?.FindItem(Resource.Id.action_search)?.ActionView?.JavaCast<SearchView>();
if (this.searchView != null)
{
this.searchView.QueryTextChange += this.OnQueryTextChangeSearchView;
this.searchView.ImeOptions = (int)ImeAction.Search;
this.searchView.MaxWidth = int.MaxValue;
this.searchView.SetBackgroundResource(Resource.Drawable.textfield_search_holo_light);
}
}
}
/// <summary>
/// Reaction on the text change event of the searchbar.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event argument.</param>
private void OnQueryTextChangeSearchView(object sender, SearchView.QueryTextChangeEventArgs e)
{
var searchPage = this.Element as SearchPage;
searchPage?.SearchCommand?.Execute(e?.NewText);
}
}
Thanks to this Stack-Overflow Thread, i got it working like a charm so far.
Now, if the user taps on a item in the "SearchPage", a new ContentPage (called "DetailPage") is pushed to the NavigationStack using the following method:
private async Task PushPageAsync(object model, ContentPage page, INavigation navigation)
{
page.BindingContext = model;
await navigation.PushAsync(page).ConfigureAwait(false);
}
That works without problems. But if the users navigates from the "DetailPage" back to the "SearchPage" by using the Back-Button, the customized Search-Header isn't showing at all.
What I tried:
Using the "OnAppearing" event of the page instead of the "OnElementChanged". That didn't solve the problem at first sight. However, if I add a Task.Delay(500) to the OnAppearing-Method and then add the SearchView again, it is displayed. But this fix seems quite ugly, and if i sleep the app and resume while using the SearchPage, the Search-Widget is showing twice.
So my question is:
Is there a bug in Xamarin or am I doing something wrong?
Is there a bug in Xamarin or am I doing something wrong?
I can't say it's a bug, I prefer to consider it is as by design. The real problem is that you're trying to modify the Toolbar in your custom renderer, and based on your description, you're using NavigationPage, its NavigationPageRenderer will update the view of Toolbar each time when current page is changed.
So you're doing right to use the "OnAppearing" event of the page instead of the "OnElementChanged", which cause another problem, the updating of Toolbar is be delayed when the old page is removed as you can see from the source code in RemovePage method, while the OnAppearing method will be executed immediately when the new page is shown:
Device.StartTimer(TimeSpan.FromMilliseconds(10), () =>
{
UpdateToolbar();
return false;
});
So I don't think you're doing anything wrong too, the method you used await Task.Delay(500); can quickly solve this issue.
Based on your description here:
But this fix seems quite ugly, and if i sleep the app and resume while using the SearchPage, the Search-Widget is showing twice.
I suggest to change your SearchPage to a View, and then dynamically add/remove this view from pages:
public class SearchPage : View
{
...
}
and renderer for it (other codes are the same, only change to inherit from ViewRenderer and the parameter of OnElementChanged is different):
public class SearchPageRenderer : ViewRenderer
{
private SearchView searchView;
/// <summary>
/// Gets or sets the toolbar.
/// </summary>
private Android.Support.V7.Widget.Toolbar toolbar;
...
/// <summary>
/// Reaction on the element changed event.
/// </summary>
/// <param name="e">The event argument.</param>
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
{
base.OnElementChanged(e);
if (e?.NewElement == null || e.OldElement != null)
{
return;
}
this.AddSearchToToolBar();
}
...
}
Then you can use it as a control/view in pages where you want to show it, not directly as a Page. For example, I want to show this search view in my MainPage, then navigate to MainPage in App.xaml.cs:
MainPage = new NavigationPage(new MainPage());
MainPage's layout:
<StackLayout>
...
</StackLayout>
At last override the OnAppearing and OnDisappearing method of the MainPage:
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(500);
var content = this.Content as StackLayout;
content.Children.Add(new SearchPage());
}
protected override void OnDisappearing()
{
base.OnDisappearing();
var content = this.Content as StackLayout;
foreach (var child in content.Children)
{
var searchpage = child as SearchPage;
if (searchpage != null)
{
content.Children.Remove(searchpage);
return;
}
}
}
By the way, if you want to navigate from MainPage here to other pages, you can code like this:
this.Navigation.PushAsync(new Page1());
There is no need to create a PushPageAsync task for it.
here is the code where I get the problem :
namespace Menu_test
{
/// <summary>
/// The main menu screen is the first thing displayed when the game starts up.
/// </summary>
class MainMenuScreen : MenuScreen
{
#region Initialization
ContentManager content;
Texture2D playgame;
Texture2D exit;
/// <summary>
/// Constructor fills in the menu contents.
/// </summary>
public MainMenuScreen()
: base()
{
// Create our menu entries.
MenuEntry playGameMenuEntry = new MenuEntry(playgame);
MenuEntry exitMenuEntry = new MenuEntry(exit);
// Hook up menu event handlers.
playGameMenuEntry.Selected += PlayGameMenuEntrySelected;
exitMenuEntry.Selected += OnCancel;
// Add entries to the menu.
MenuEntries.Add(playGameMenuEntry);
MenuEntries.Add(exitMenuEntry);
}
public override void LoadContent()
{
if (content == null)
content = new ContentManager(ScreenManager.Game.Services, "Content");
Art.Load(content);
playgame = Art.PlayGame;
exit = Art.Exit;
if (playgame==null)
throw new ArgumentNullException();
}
/// <summary>
/// Unloads graphics content for this screen.
/// </summary>
public override void UnloadContent()
{
content.Unload();
}
#endregion
#region Handle Input
/// <summary>
/// Event handler for when the Play Game menu entry is selected.
/// </summary>
void PlayGameMenuEntrySelected(object sender, PlayerIndexEventArgs e)
{
LoadingScreen.Load(ScreenManager, true, e.PlayerIndex,
new GameplayScreen());
}
/// <summary>
/// Event handler for when the Options menu entry is selected.
/// </summary>
/// <summary>
/// When the user cancels the main menu, ask if they want to exit the sample.
/// </summary>
protected override void OnCancel(PlayerIndex playerIndex)
{
ScreenManager.Game.Exit();
}
#endregion
}
}
So in the MainMenuScreen() my playgame is null, but in LoadContent() it is not null. Basically what I want to do is to pass a Texture2D as a parameter to the MenuEntry class, but the texture is null even before being passed to the class.
You can download the full project here if you want to try run it.
Thank you for reading.
LoadContent is called by XNA after your MainMenuScreen is initialized. You may have to defer the usage of your playGame object to the moment when the texture is loaded or initialize your MenuEntries in the LoadContent.
I am attempting to build an MVVM Windows Application Using PRISM 5 and I have wrapped my main content window with an AvalonDock (Wrapping Code Below).
using Microsoft.Practices.Prism.Regions;
using Xceed.Wpf.AvalonDock;
using System.Collections.Specialized;
using System.Windows;
using Xceed.Wpf.AvalonDock.Layout;
namespace Central.Adapters
{
using System.Linq;
public class AvalonDockRegionAdapter : RegionAdapterBase<DockingManager>
{
/// <summary>
/// This ties the adapter into the base region factory.
/// </summary>
/// <param name="factory">The factory that determines where the modules will go.</param>
public AvalonDockRegionAdapter(IRegionBehaviorFactory factory)
: base(factory)
{
}
/// <summary>
/// Since PRISM does not support the Avalon DockingManager natively this adapter provides the needed support.
/// </summary>
/// <param name="region">This is the region that resides in the DockingManager.</param>
/// <param name="regionTarget">The DockingManager that needs the window added.</param>
protected override void Adapt(IRegion region, DockingManager regionTarget)
{
region.Views.CollectionChanged += (sender, e) =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
AddAnchorableDocument(regionTarget, e);
break;
case NotifyCollectionChangedAction.Remove:
break;
}
};
}
/// <summary>
/// This adds the window as an anchorable document to the Avalon DockingManager.
/// </summary>
/// <param name="regionTarget">The DockingManager instance.</param>
/// <param name="e">The new window to be added.</param>
private static void AddAnchorableDocument(DockingManager regionTarget, NotifyCollectionChangedEventArgs e)
{
foreach (FrameworkElement element in e.NewItems)
{
var view = element as UIElement;
var documentPane = regionTarget.Layout.Descendents().OfType<LayoutDocumentPane>().FirstOrDefault();
if ((view == null) || (documentPane == null))
{
continue;
}
var newContentPane = new LayoutAnchorable
{
Content = view,
Title = element.ToolTip.ToString(),
CanHide = true,
CanClose = false
};
documentPane.Children.Add(newContentPane);
}
}
/// <summary>
/// This returns the region instance populated with all of its contents.
/// </summary>
/// <returns>DockingManager formatted region.</returns>
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
}
}
I then register this adapter in the bootstrapper this way:
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
var mappings = base.ConfigureRegionAdapterMappings();
if (mappings == null)
{
return null;
}
mappings.RegisterMapping(typeof(DockingManager), new AvalonDockRegionAdapter(ConfigureDefaultRegionBehaviors()));
return mappings;
}
The problem that I am facing is that other region UI elements will require certain LayoutAnchorable windows to become the active and selected window. The content I am feeding into the LayoutAnchorable object is a ContentControl.
In my View's ViewModel I have a property that I am successfully setting using another UI element's interaction. However I am unable to make the connection from ViewModel(Property) -> ContentContro(View) -> LayoutAnchorable(View's Parent).IsSelected or ,IsActive.
I know how to bind to a parent object but that eats up the property and does not allow me to bind it to the ViewModel property as well. I also have no problem binding to a ViewModel property, but that is useless unless I can get it to set the parent property. I have also attempted View based events. Problem with this is that once the view loads it doe not like calling its own events anymore unless it is caused by user interaction directly with that view.
In short I just want to display the appropriate window when needed based on an interaction in another part of my program. Maybe I am making this more complicated than it needs to be. Any assistance on this would be greatly appreciated.
Thanks
James
As I took a break from the problem at had I looked at it from another perspective. To solve the issue I decided to store the instance of the content panes containing the views into a singleton dictionary class:
using System;
using System.Collections.Generic;
using Xceed.Wpf.AvalonDock.Layout;
namespace Central.Services
{
public class DockinWindowChildObjectDictionary
{
private static Dictionary<string, LayoutAnchorable> _contentPane = new Dictionary<string, LayoutAnchorable>();
private static readonly Lazy<DockinWindowChildObjectDictionary> _instance =
new Lazy<DockinWindowChildObjectDictionary>(()=> new DockinWindowChildObjectDictionary(), true);
public static DockinWindowChildObjectDictionary Instance
{
get
{
return _instance.Value;
}
}
/// <summary>
/// Causes the constructor to be private allowing for proper use of the Singleton pattern.
/// </summary>
private DockinWindowChildObjectDictionary()
{
}
/// <summary>
/// Adds a Content Pane instance to the dictionary.
/// </summary>
/// <param name="title">The title given to the Pane during instantiation.</param>
/// <param name="contentPane">The object instance.</param>
public static void Add(string title, LayoutAnchorable contentPane)
{
_contentPane.Add(title, contentPane);
}
/// <summary>
/// If a window needs to be removed from the dock this should be used
/// to also remove it from the dictionary.
/// </summary>
/// <param name="title">The title given to the Pane during instantiation.</param>
public static void Remove(string title)
{
_contentPane.Remove(title);
}
/// <summary>
/// This will return the instance of the content pane that holds the view.
/// </summary>
/// <param name="title">The title given to the Pane during instantiation.</param>
/// <returns>The views Parent Instance.</returns>
public static LayoutAnchorable GetInstance(string title)
{
return _contentPane[title];
}
}
}
In the adapter I modified this code as follows:
private static void AddAnchorableDocument(DockingManager regionTarget, NotifyCollectionChangedEventArgs e)
{
foreach (FrameworkElement element in e.NewItems)
{
var view = element as UIElement;
var documentPane = regionTarget.Layout.Descendents().OfType<LayoutDocumentPane>().FirstOrDefault();
if ((view == null) || (documentPane == null))
{
continue;
}
var newContentPane = new LayoutAnchorable
{
Content = view,
Title = element.ToolTip.ToString(),
CanHide = true,
CanClose = false
};
DockinWindowChildObjectDictionary.Add(element.ToolTip.ToString(),** newContentPane);
documentPane.Children.Add(newContentPane);
}
}
Then I added the following to the ViewModel to gain the effect I was going after:
public void OnNavigatedTo(NavigationContext navigationContext)
{
var viewParentInstance = DockinWindowChildObjectDictionary.GetInstance("Belt Plan");
viewParentInstance.IsSelected = true;
}
One hurdle done and on to the next. For a base to all the information in this post the ViewSwitchingNavigation.sln included with the PRISM 5.0 download will get you started. If you are wondering about the ConfigureDefaultRegionBehaviors() referenced in the adapter registration I got that from the StockTraderRI_Desktop.sln in the sample downloads.
I hope this post helps someone else that finds themselves in the same pickle this technology sandwich provides.
Sincerely
James
I have OnMouseEnter and OnMouseLeave event handlers setup for my form. When the mouse moves over the form I want to set the opacity to 100% and when it moves away I want to set it to 25%. It works well, except when the mouse moves over one of the buttons on the form. The OnMouseLeave event fires and hides the form again. Is there a good way to handle this, without having to wire up OnMouseEnter for every control on the form?
EDIT: I'm going to leave this answer here, even though it can't be made to work reliably. The reason: to prevent somebody else from trying the same thing. See end of message for the reason it won't work.
You can do this fairly easily for the client rectangle by getting the cursor position and checking to see if it's within the Form's client area:
private void Form1_MouseLeave(object sender, EventArgs e)
{
Point clientPos = PointToClient(Cursor.Position);
if (!ClientRectangle.Contains(clientPos))
{
this.Opacity = 0.25;
}
}
This assumes that none of your child controls will be changing the opacity.
However, you'll find that it's a less than perfect solution, because when the mouse goes to the title bar, the Form goes to 0.25%. You could fix that by checking to see if the mouse position is within the window rect (using the Bounds property), but then your window will remain opaque if the mouse moves off the title bar and out of the window.
You have a similar problem when entering the title bar from outside.
I think you'll have to handle the WM_NCMOUSEENTER and WM_NCMOUSELEAVE messages in order to make this work reliably.
Why it can't work:
Even handling the non-client area notifications can fail. It's possible for the mouse to enter on a child control, which would prevent the Form from being notified.
I think it is impossible to do, without handling the MouseEnter and MouseLeave events of all the children, but you do not have to wire them manually.
Here is some code I copied & pasted from a project of mine. It does almost what you described here. I actually copied the idea and the framework from this site.
In the constructor I call the AttachMouseOnChildren() to attach the events.
The OnContainerEnter and OnContainerLeave are used to handle the mouse entering/leaving the form itself.
#region MouseEnter & Leave
private bool _childControlsAttached = false;
/// <summary>
/// Attach enter & leave events to child controls (recursive), this is needed for the ContainerEnter &
/// ContainerLeave methods.
/// </summary>
private void AttachMouseOnChildren() {
if (_childControlsAttached) {
return;
}
this.AttachMouseOnChildren(this.Controls);
_childControlsAttached = true;
}
/// <summary>
/// Attach the enter & leave events on a specific controls collection. The attachment
/// is recursive.
/// </summary>
/// <param name="controls">The collection of child controls</param>
private void AttachMouseOnChildren(System.Collections.IEnumerable controls) {
foreach (Control item in controls) {
item.MouseLeave += new EventHandler(item_MouseLeave);
item.MouseEnter += new EventHandler(item_MouseEnter);
this.AttachMouseOnChildren(item.Controls);
}
}
/// <summary>
/// Will be called by a MouseEnter event, with any of the controls within this
/// </summary>
void item_MouseEnter(object sender, EventArgs e) {
this.OnMouseEnter(e);
}
/// <summary>
/// Will be called by a MouseLeave event, with any of the controls within this
/// </summary>
void item_MouseLeave(object sender, EventArgs e) {
this.OnMouseLeave(e);
}
/// <summary>
/// Flag if the mouse is "entered" in this control, or any of its children
/// </summary>
private bool _containsMouse = false;
/// <summary>
/// Is called when the mouse entered the Form, or any of its children without entering
/// the form itself first.
/// </summary>
protected void OnContainerEnter(EventArgs e) {
// No longer transparent
this.Opacity = 1;
}
/// <summary>
/// Is called when the mouse leaves the form. When the mouse leaves the form via one of
/// its children, this will also call OnContainerLeave
/// </summary>
/// <param name="e"></param>
protected void OnContainerLeave(EventArgs e) {
this.Opacity = DEFAULT_OPACITY;
}
/// <summary>
/// <para>Is called when a MouseLeave occurs on this form, or any of its children</para>
/// <para>Calculates if OnContainerLeave should be called</para>
/// </summary>
protected override void OnMouseLeave(EventArgs e) {
Point clientMouse = PointToClient(Control.MousePosition);
if (!ClientRectangle.Contains(clientMouse)) {
this._containsMouse = false;
OnContainerLeave(e);
}
}
/// <summary>
/// <para>Is called when a MouseEnter occurs on this form, or any of its children</para>
/// <para>Calculates if OnContainerEnter should be called</para>
/// </summary>
protected override void OnMouseEnter(EventArgs e) {
if (!this._containsMouse) {
_containsMouse = true;
OnContainerEnter(e);
}
}
#endregion
I think one way to reliably handle the mouse events you're interested is to set up an IMessageFilter on your Application object from which you can intercept all mouse messages (WM_MOUSEMOVE etc ..) even if they are sent to child controls of the form.
Here's some demo code:
using System;
using System.Windows.Forms;
namespace Test
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
public static Form frm = null;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
frm = new Form1 {Opacity = 0.25};
frm.Controls.Add(new Button{Dock = DockStyle.Fill, Text = "Ok"});
Application.AddMessageFilter(new MouseMoveFilter());
Application.Run(frm);
}
}
public class MouseMoveFilter : IMessageFilter
{
#region IMessageFilter Members
private const int WM_MOUSELEAVE = 0x02A3;
private const int WM_NCMOUSEMOVE = 0x0A0;
private const int WM_MOUSEMOVE = 0x0200;
private const int WM_NCMOUSELEAVE = 0x2A2;
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_NCMOUSEMOVE:
case WM_MOUSEMOVE:
Program.frm.Opacity = 1;
break;
case WM_NCMOUSELEAVE:
case WM_MOUSELEAVE:
if (!Program.frm.Bounds.Contains(Control.MousePosition))
Program.frm.Opacity = 0.25;
break;
}
return false;
}
#endregion
}
}
Alternatively you can inherit from Form class and override PreProcessMessage() to accomplish the same thing ...