Implementing Detachable Panel in UWP App - xaml

I have an Image in a grid where I display some custom content by setting the Image's source to a WritableBitmap and updating the bitmap. What I want to do is to implement a "detach" button that will put my Image on a separate window allowing the user to move it to a different screen, resize it etc. independent of my main app window. If the new window is closed, I would like to bring it back to its original spot. While the Image is on the new window, I want to continuously update it with new content via updating source bitmap (as it would have been before it was detached).
I initially thought I would be able to create a new window and "move" my Image control there by first removing it from its original parent then adding it as a child to a layout in the new window. I used the code below:
CoreApplicationView^ newCoreView = CoreApplication::CreateNewView();
int mainViewId = Windows::UI::ViewManagement::ApplicationView::GetApplicationViewIdForWindow(
CoreApplication::MainView->CoreWindow);
uint indexOfObjectToDetach = -1;
bool found = originalGrid->Children->IndexOf(imageToMove, &indexOfObjectToDetach);
if(found)
{
myGrid->Children->RemoveAt(indexOfObjectToDetach);
}
DispatchedHandler^ dispatchHandler = ref new DispatchedHandler([this, mainViewId]()
{
newView_ = Windows::UI::ViewManagement::ApplicationView::GetForCurrentView();
Windows::UI::Xaml::Controls::StackPanel^ newWindowGrid = ref new Windows::UI::Xaml::Controls::StackPanel();
Window::Current->Content = newWindowGrid;
Window::Current->Activate();
newWindowGrid->Children->Append(imageToMove); // Add to new parent
});
create_task(newCoreView->Dispatcher->RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, dispatchHandler)).then([this, mainViewId]()
{
auto a = newView_->Id;
create_task(ApplicationViewSwitcher::TryShowAsStandaloneAsync(a, ViewSizePreference::Default, mainViewId, ViewSizePreference::Default));
});
However in the line where I add the Image to its new parent, I get an Interface was marshalled for a different thread error. Upon more reading, this is due to the fact that each new window is in its own thread and I'm moving an object to another thread.
I am new to UWP and I am not sure how to approach implementing this UI behavior. How do I access/transfer my state in one view to another ?

The problem is indeed the fact that each application view in UWP has its own thread and its own UI dispatcher. When you create a control, it is tied to the UI thread it was created on, hence you cannot place it onto another application view.
The solution is to create the new Image next to the StackPanel within the new view's UI thread. I don't really use C++, but in C# I would implement it as follows:
await newCoreView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
StackPanel panel = new StackPanel();
Image image = new Image();
panel.Children.Add( panel );
image.Source = ...; //your source
Window.Current.Content = frame;
Window.Current.Activate();
newViewId = ApplicationView.GetForCurrentView().Id;
});
To further clarify - you can safely "transfer" normal data types into other view, the problem is mainly with the UI-tied types like controls, pages, etc.

Related

adding textfield over movieclip images

I have several images, which are Symbols (movieClip) with Alpha parameter.
And i'm creating dynamic textfield from AS3 to be able change text every few seconds.
Problem is that everything worked good till i converted images to MovieClips. But after that my textfields are not visible.
Here is the code:
textFormat = new TextFormat();
textfield = new TextField();
textFormat.font = new customFonts().fontName;
textFormat.size = 16;
textFormat.align = "center";
textFormat.color = 0xFFFFFF;
textfield.defaultTextFormat = textFormat;
textfield.embedFonts = true;
textfield.width = 480;
textfield.height = 95;
textfield.x = 185;
textfield.y = 22;
textfield.wordWrap = true;
addChild (textfield);
So the question is - how to bring this textfield to the top so it would be visible?
You're adding your Text field after the movie clips are being initiated. Think of it as a layer, the text field is at the bottom layer, hence they will not be seen.
I would look at the container class
The Container class is an abstract base class for components that controls the layout characteristics of child components. You do not create an instance of Container in an application. Instead, you create an instance of one of Container's subclasses, such as Canvas or HBox.
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/core/Container.html
You should be able to change what is displayed.
EDIT:
Anytime you add a clip it is added on top by default.
You should also look into Z-Index.
If you are coding with Flash Develop then it can get tricky, whilst using Flash Adobe CC can make your life so much easier!
Sorry if it's not that much of an answer.

Windows 8 app: How to pass a parameter on GoBack()?

There are a lot of samples showing how to pass a parameter on navigating to a page in Window 8 (WinRT). But I could not find any hint for passing parameters going back.
Situation: The user navigates to a details page of same data. The data is passed to the page by
Frame.Navigate(typeof(DedtailsPage), data);
How can I pass back the changed data on GoBack()?
Store the reference to data somewhere and retrieve it when you navigate back?
Also note that it's best not to pass objects other than simple strings or values between pages, since only simple types are supported for storing frame navigation state in case your app gets suspended.
I know, that this is a very bad idea, but usualy I use this:
To write:
Frame rootFrame = Window.Current.Content as Frame;
rootFrame.Tag = myObject1;
To read:
Frame rootFrame = Window.Current.Content as Frame;
var myObject2 = rootFrame.Tag;
I've made another choice to handle this.
Keep in mind that I'm a Xamarin developer (not Forms!) so i was looking for a solution which was similar for all platforms: iOS, Android and Windows.
I am a great fun of events, rather than passing objects or storing globals.
So the best choice to pass data from PageB (the child) to PageA (the parent) is to communicate via events.
Some notes: In iOS and Android, when you navigate from a "page" to "page" you can do this by passing an instance of the target object you want to navigate to. In iOS you create an instance of a custom UIViewController. In Android you create an instance of a custom Fragment. Doing this allow you to attach events handler to your instances.
An example:
var controller = new ExtrasViewController();
controller.ExtraSelected += ExtrasFragment_ExtraSelected;
controller.ExtrasCleared += ExtrasFragment_ExtrasCleared;
this.NavigationController.PushViewController(controller, false);
In WinRT Apps you are only allowed to pass "types" of target page to navigate to. So the only way to attach event handlers to your page instance is to use the OnNavigatedFrom method from the calling page. So suppose you are in PageA and want to attach some event handlers to your PageB, before it become active, simply write in your PageA "code behind":
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
var page = e.Content as ExtraBody;
if(page != null)
{
page.ExtraSelected += ExtrasFragment_ExtraSelected;
page.ExtrasCleared += ExtrasFragment_ExtrasCleared;
}
}

How to set Background of a Button without flicker?

I am trying to change the Background of a button to an image source. I want to load that image in memory when we navigate to the page so that it doesn't flicker the first time it shows.
On Windows Phone, I was able to create the image source as such:
StreamResourceInfo resourceInfo = Application.GetResourceStream(uri);
BitmapImage bitmapSource = new BitmapImage();
// Avoid flicker by not delay-loading.
bitmapSource.CreateOptions = BitmapCreateOptions.None;
bitmapSource.SetSource(resourceInfo.Stream);
imageSource = bitmapSource;
I tried something similar in my Windows 8 Store app:
BitmapImage bitmapSource = new BitmapImage();
bitmapSource.CreateOptions = BitmapCreateOptions.None;
bitmapSource.UriSource = uri;
imageSource = bitmapSource;
but the same problem occurs. The button already has a different image as the Background, and on a certain event I would like it to change to the new background. But when I change the source, a noticeable flicker is observed. I'm assuming this is because the image is not yet in memory, as the issue goes away the second time the image source is modified.
Anyone know a solution? I need to somehow force the loading of this image.
Thanks!
If you use the SetSourceAsync method on the BitmapImage and await it before you attach it to the image source you should not see the flicker:-
// Ensure the stream is disposed once the image is loaded
using (IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
// Set the image source to the selected bitmap
BitmapImage bitmapImage = new BitmapImage();
await bitmapImage.SetSourceAsync(fileStream);
imageSource = bitmapImage;
}
The MSDN docs have some more info on this
Thanks Ross, but what I ended up doing instead is I preloaded the half dozen or so bitmaps I needed by using similar code to what you had above, except from resource of course. I did this asynchronously when the page loaded, and then when I set the ImageSource on the button background, I used the already preloaded bitmaps. That way I know I'm not allocated a new chunk of memory for every instance of the bitmap.

Reusing Views and Viewmodel with MEF & Silverlight

Here is what I'd like to do :
I have an Silverlight application using navigation frame and MEF. (like this one : http://msdn.microsoft.com/en-us/magazine/gg535672.aspx)
This application consists of a set of buttons. Each button click load a view and its associated ViewModel.
Within theses views, I've a list with items and when I click on each items it refreshs a kind of sub-view in this view.
I'd like to create a navigation system : for example myapp.aspx#view1/2, where 2 is in fact the item clicked in the list. If I click on one of the button, it would load a default item and refresh all the view, but when I click on an item, I wouldn't like to refresh all the view but only certain part of the view (I do not want to create another instance of the view and viewmodel).
My problem is in fact that I would like to get the best pratice to get a reference to an existing view or viewmodel when i'm navigating to this page that has already been loaded (for example from myapp.aspx#view1/2 to myapp.aspx#view1/3)(I plan to do this into the BeginLoad of the ContentLoader class)
If I get the viewmodel, I can do that I want by changing for example the current itemId property which could refresh the view thanks to binding.
Thanks in davance if you have something to propose.
A common approach is to use some form of Messenger to do this type of operation. The item's click could trigger the sending of a message, with the Item attached. The ViewModel in question would be a subscriber, and edit its current settings (ie: it's ItemId, which would trigger the binding refresh).
The most common implementations are usually ones similar to the Messenger service in MVVM Light.
It's fairly easy to roll your own here, though, especially since you're already using MEF. Just create a service to handle the message passing, and import it into both endpoints.
Actually, I would have prefered to use an URI to navigate in my application when I click on an item, but if I use an URI, the entire view is reloading and not the specific part I'd like to.
With the messenger, I won't be able to use navigation with url within the view, I think ? Or else I didn't really figure out what you proposed to me.
The algorithm I would like to take is :
navigate("...asp#MyView1/1")
MyView1 is current view ?
yes then I'd like to get the viewmodel of the current view and change it the ItemId property with 1
no, then the view will be created
And I'd like to implement this algorithm there : (this is the place where the view is instancied for each navigation, in my CompositionNavigationContentLoader class)
public IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, AsyncCallback userCallback, object asyncState)
{
// Convert to a dummy relative Uri so we can access the host.
var relativeUri = new Uri("http://" + targetUri.OriginalString, UriKind.Absolute);
// Get the factory for the ViewModel.
var viewModelMapping = ViewModelExports.FirstOrDefault(o => o.Metadata.Key.Equals(relativeUri.Host, StringComparison.OrdinalIgnoreCase));
if (viewModelMapping == null)
throw new InvalidOperationException(
String.Format("Unable to navigate to: {0}. Could not locate the ViewModel.", targetUri.OriginalString));
// Get the factory for the View.
var viewMapping = ViewExports.FirstOrDefault(o => o.Metadata.ViewModelContract == viewModelMapping.Metadata.ViewModelContract);
if (viewMapping == null)
throw new InvalidOperationException(
String.Format("Unable to navigate to: {0}. Could not locate the View.", targetUri.OriginalString));
// Resolve both the View and the ViewModel.
var viewFactory = viewMapping.CreateExport();
var view = viewFactory.Value as Control;
var viewModelFactory = viewModelMapping.CreateExport();
var viewModel = viewModelFactory.Value as IViewModel;
// Attach ViewModel to View.
view.DataContext = viewModel;
viewModel.OnLoaded();
Thanks.

Windowless (not chromeless) Adobe AIR app

What would be the best way to go about building an Adobe AIR app that doesn't have any windows (i.e. exists only in the system tray / dock)? I noticed that the default base tag in Flash Builder is <s:WindowedApplication> which seems to imply there'll be a window.
Should I just use <s:WindowedApplication> and call window.hide()? I saw there's another base class, <s:Application>, but I got the sense that was more for files that run in the browser. It seems like using window.hide() would briefly flash a window when the application starts which could confuse users. However I'd also ideally like to retain the ability to have the app open a window later if needed, or also to change the application from tray-only to windowed through an update.
You need to edit the app-config file to enable transparent chrome and visible = false. Then you need to change the WindowedApplication tag to and app your custom skin. You need to add control buttons for close etc, since that functionality isn't present in a web-app (since you have changed the tag). Also you need to add drag functionality. If you like to make your application re-sizable you need to add that too, manually.
In your manifest (-app.xml) file set systemChrome to none and transparent to true. The visible property is irrelevant, and the default is false anyway so ignore it.
you'll have to tweak this, import whatever classes are missing, etc... you could also do it as an mxml component and just set visible and enabled to false on the root tag. Fill up the trayImages array with the icons you want in the dock.
p
ackage{
import spark.components.WindowedApplication;
public class HiddenApplication extends WindowedApplication{
public function HiddenApplication(){
super();
enabled=false;
visible=false;
var trayImages:Array;
if(NativeApplication.supportsDockIcon||NativeApplication.supportsSystemTrayIcon){
NativeApplication.nativeApplication.activate();
var sep:NativeMenuItem = new NativeMenuItem(null,true);
var exitMenu:NativeMenuItem = new NativeMenuItem('Exit',false);
exitMenu.addEventListener(Event.SELECT,shutdown);
var updateMenu:NativeMenuItem = new NativeMenuItem('Check for Updates',false);
updateMenu.addEventListener(Event.SELECT,upDcheck);
var prefsMenu:NativeMenuItem = new NativeMenuItem('Preferences',false);
prefsMenu.addEventListener(Event.SELECT,Controller.showSettings);
NativeApplication.nativeApplication.icon.addEventListener(ScreenMouseEvent.CLICK,showToolBar);
if(NativeApplication.supportsSystemTrayIcon){
trayIcon = SystemTrayIcon(NativeApplication.nativeApplication.icon);
setTrayIcons();
trayIcon.tooltip = "Some random tooltip text";
trayIcon.menu = new NativeMenu();
trayIcon.menu.addItem(prefsMenu);
trayIcon.menu.addItem(sep);
trayIcon.menu.addItem(updateMenu);
trayIcon.menu.addItem(exitMenu);
}
else{
dockIcon = DockIcon(NativeApplication.nativeApplication.icon);
setTrayIcons();
dockIcon.menu = new NativeMenu();
dockIcon.menu.addItem(prefsMenu);
dockIcon.menu.addItem(sep);
dockIcon.menu.addItem(updateMenu);
dockIcon.menu.addItem(exitMenu);
}
}
function setTrayIcons(n:Number=0):void{
if(showTrayIcon&&(trayIcon||dockIcon)){
Controller.debug('Updating tray icon');
if(NativeApplication.supportsSystemTrayIcon){
trayIcon.bitmaps = trayImages;
}
else if(NativeApplication.supportsDockIcon){
dockIcon.bitmaps = trayImages;
}
}
else if(trayIcon||dockIcon) trayIcon.bitmaps = new Array();
}
}
}