UWP C# pass a variable to another thread - c#

Im new to programming, and after 7 days of searching I couldn't find a solution.
My "MainPage" opens, and I click to Open a "SecondaryPage" which opens on a new thread window with core Dispatcher. My "MainPage" has a button Click event which updates "TextBlock1". What I cant achieve is to pass this value to my "SecondPage" "TextBlock2".
MainPage.xaml below.
<StackPanel>
<Button Click="Button_Click_NewWindow" Content="Start a new window" FontSize="30"/>
<TextBlock Name="Textblock1" Text="Empty" FontSize="30"/>
<Button Click="Button_Click_Add" Content="+1" FontSize="30"/>
</StackPanel>
Code Behind SecondPage.xaml
<StackPanel>
<TextBlock Name="Textblock2" Text="Empty" FontSize="30"/>
</StackPanel>
MainPage.xaml.cs
namespace MulitViewV1
{
public sealed partial class MainPage : Page
{
public int T1G = 0;
public MainPage()
{
this.InitializeComponent();
}
private async void Button_Click_NewWindow(object sender, RoutedEventArgs e)
{
CoreApplicationView newView = CoreApplication.CreateNewView();
int newViewId = 0;
await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Frame frame = new Frame();
frame.Navigate(typeof(SecondPage), null);
Window.Current.Content = frame;
// You have to activate the window in order to show it later.
Window.Current.Activate();
newViewId = ApplicationView.GetForCurrentView().Id;
});
bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
}
private async void Button_Click_Add(object sender, RoutedEventArgs e)
{
T1G = T1G + 1;
Textblock1.Text = T1G.ToString();
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
//UI code here
// NOT WORKING error "TextBlock2" does not exist in the current context
Textblock2.Text = T1G.ToString();
});
}
}
}
I see the Dispatcher is working to my MainPage View, but I cant figure out code to direct it to my SecondPage "newViewID".

There's no need to call Dispatcher all the time. You button clicks are coming to you on the UI thread so the handlers are also going to be on the UI thread.
A way to think about it is that you're always on the UI thread unless you're doing something with *Async. If you await async stuff, you're good. This is not true all of the time, but it's a place to start and handle the problems you might encounter as the come.
Generally you only want a single Frame in your application. Multiple Frames (and multiple windows) is an advanced topic; save it for later.
By default, you UWP app will contain a Frame. Pages are hosted in this frame. The frame can navigate between the pages it hosts. Think of the frame as you browser window, and the pages are like web pages.
You can get the Frame by getting the Frame property on the page. To navigate to a new page you do this:
private async void Button_Click_NewWindow(object sender, RoutedEventArgs e)
{
Frame.Navigate(typeof(SecondPage), null);
}
Next you want to change the text of TextBlock2. TextBlock2 is located on SecondPage, but your code is in MainPage, which has no TextBlock2, and the compiler is telling you that TextBlock2 isn't a thing in MainPage.
Inside your SecondPage.xaml.cs you are however able to get to TextBlock2 as it is present there. Remember though, MainPage will not be visible after navigation to SecondPage (just as you can't see two web pages at the same time when navigating from one to another).

In your Button_Click_NewWindow where you are calling
frame.Navigate(typeof(SecondPage), null);
instead of null, pass the text from TextBlock like below
string TextBoxText = Textblock1.Text;
frame.Navigate(typeof(SecondPage), TextBoxText);
And in your SecondPage, Override OnNavigated to method and you can get the Text like below.
string PassedText=e.parameter.ToString();
and you can assign this to your TextBlock as below
Textblock2.Text=PassedText;
Update
Change your Button_Click_NewWindow to below and add the two variables on top.
int newViewId = 0;
Window SecondWindow;
private async void Button_Click_NewWindow(object sender, TappedRoutedEventArgs e)
{
string TextBoxText = Textblock1.Text;
int mainViewId = ApplicationView.GetApplicationViewIdForWindow(CoreApplication.MainView.CoreWindow);
if (newViewId == 0)
{
CoreApplicationView newCoreView = CoreApplication.CreateNewView();
ApplicationView newAppView = null;
await newCoreView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
newAppView = ApplicationView.GetForCurrentView();
Window.Current.Content = new Frame();
(Window.Current.Content as Frame).Navigate(typeof(SecondPage), TextBoxText);
Window.Current.Activate();
SecondWindow = Window.Current;
});
newViewId = newAppView.Id;
}
else
{
await SecondWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
SecondWindow.Content = new Frame();
(SecondWindow.Content as Frame).Navigate(typeof(SecondPage), TextBoxText);
SecondWindow.Activate();
});
}
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId, ViewSizePreference.UseHalf, mainViewId, ViewSizePreference.UseHalf);
}
And your output

Related

Refreshing a binded property from another Thread doesn't work

In a Xamarin project, I am trying to update a label showed in a loading page.
The loading page contains also an activity indicator control and is showed after the user clicks OK on a display alert and the alert is showed after the user clicks on a button in the UI.
The loading page is necessary because some heavy activity happens in background. Every seconds I query the status of the activity and I want this status be showed on a loading page, giving the user some information about the activity. This activity is wrappred in a ICommand in the ViewModel.
When I run the command I want a variable in the viewModel referred by the page be updated.
The command is executed in a Task.Run method otherwise the loading page is not showed untill the command has done.
This is the code:
View code behind
private async void SelfLearningButton_OnClicked(object sender, EventArgs e)
{
var bindingContext = BindingContext as AutomationViewModel;
if (await DisplayAlert("message",
"OK", "Cancel"))
{
bindingContext.IsLearning = true; //a variable that drives the ISVisibleProperty of the loading page
//Task.Run because the loading page must be showed otherwise I wait for the command to be completed (with async/await too)
_ = Task.Run(() => bindingContext.TheCommand.Execute(null));
}
}
View.xaml
<Label IsVisible="{Binding IsLearning}"
TextColor="#f1c00e"
Padding="0, 20, 0, 0"
HorizontalOptions="CenterAndExpand"
VerticalOptions="FillAndExpand"
FontAttributes="Bold"
x:Name="LearningStatus"
Text="{Binding Status}">
View Model
//The variable that must be updated
public string Status
{
private set
{
if (_status != value)
{
_status = value;
NotifyPropertyChanged();
}
}
get { return _status ; }
}
TheCommand= new Command(
async () =>
{
try
{
int i = 0;
while (i < 10000 )
{
++i;
//Doesn't work
Device.BeginInvokeOnMainThread( ()=> _status= i.ToString()); //sorry for the boxing
}
}
catch(Exception e)
{
//
}
finally
{
IsLearning = false;
}
});
When I run the code of the command the BeginInvokeOnMainThread doesn't update the variable. I use BeginInvokeOnMainThread because view items must be updated in the UI thread. At every iteration _status has the same value used for init it, and setter code of the field is not called. What is going wrong with this code?
Thank you

Why incorrect order of navigation from SplashScreen to MainPage

I trying to understand an example app by Microsoft. Now I can't repeat the next part. The app have classes App and ExtendedSplach. I want to repeat loading by ExtendedSplash. In my case, it's simple switch from splash to main page after some delay.
Introduction
The example to do like this.
If app runs with breakpoints on line .Content = extendedSplash and .Content = rootFrame, then the first will be extendedSplash. But line .Content = extendedSplash the follow after .Content = rootFrame. The constructor ExtendedSplash calls LoadDataAsync that set .Content = rootFram by first.
However, method LoadDataAsync contains await call
await Startup.ConfigureAsync();
I think that thus the first will extendedSplash. And we will see loading page.
class App
...
bool loadState = (e.PreviousExecutionState == ApplicationExecutionState.Terminated);
ExtendedSplash extendedSplash = new ExtendedSplash(e, loadState);
Window.Current.Content = extendedSplash;
Window.Current.Activate();
class ExtendedSplash
public ExtendedSplash(IActivatedEventArgs e, bool loadState)
{
...
LoadDataAsync(this.activatedEventArgs);
}
private async void LoadDataAsync(IActivatedEventArgs e)
{
...
rootFrame.Navigate(typeof(LoginView), shellArgs);
Window.Current.Content = rootFrame;
Window.Current.Activate();
}
Problem
I tried to repeat the same. I want see loading and then swith to other page. But my case with breakpoints looks like the first .Content = rootFrame and the second .Content = extendedSplash. Thus my queue are logo app with delay 5 seconds and then page with extendedSplash. The page rootFrame losing.
I will grateful for any help.
My code
I did the same by App class
bool loadState = (e.PreviousExecutionState == ApplicationExecutionState.Terminated);
ExtendedSplash extendedSplash = new ExtendedSplash(e, loadState);
Window.Current.Content = extendedSplash;
Window.Current.Activate();
And the next by ExtendedSplash
public ExtendedSplash(IActivatedEventArgs e, bool loadState)
{
this.InitializeComponent();
Window.Current.SizeChanged += new WindowSizeChangedEventHandler(ExtendedSplash_OnResize);
this.splashScreen = e.SplashScreen;
this.activatedEventArgs = e;
OnResize();
rootFrame = new Frame();
LoadDataAsync(activatedEventArgs);
}
private async void LoadDataAsync(IActivatedEventArgs e)
{
await Test();
rootFrame.Navigate(typeof(MainPage));
Window.Current.Content = rootFrame;
Window.Current.Activate();
}
private async Task Test()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
while (stopwatch.ElapsedMilliseconds < 5000) ;
}
The problem with your code is in fact in the Test() method. You have marked is async, but that doesn't make the method asynchronous. Instead, your code will actually be stuck for five seconds in blocking manner in the while loop.
Try the following version:
private async Task Test()
{
await Task.Delay(5000);
}
This form of code is in fact asynchronous, so as a result the UI thread will be free to display the splash screen in the meantime.
In general - async methods run on the thread that calls them until they hit an "actual" asynchronous code - for example I/O bound async method or when you run your code with await Task.Run(()=>{...}.

UWP Custom Constructor when creating a secondary window

I'm developing a quick application with the sole purpose of using picture-in-picture mode (compact view) in UWP to display Youtube videos over top of my work. Here's the way the current system works:
MainPage - Handles searching of youtube videos
YoutubeItem - A usercontrol that the mainpage creates for each youtube result. Approximately 50 of these are put into a wrap panel.
YoutubeViewer - A seperate page that runs in it's own window and displays the youtube video.
Here's my issue. I store all the information for the youtube video in each of the YoutubeItems. Using a button, I record the click event and handle it. Here's the code for handling the click:
private async void Button_Click(object sender, RoutedEventArgs e)
{
CoreApplicationView newView = CoreApplication.CreateNewView();
int newViewId = 0;
await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Frame frame = new Frame();
frame.Navigate(typeof(YoutubeViewer), null);
Window.Current.Content = frame;
// You have to activate the window in order to show it later.
Window.Current.Activate();
newViewId = ApplicationView.GetForCurrentView().Id;
});
bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
}
The problem arises when I have to send the link for the video to the YoutubeViewer. Originally, I was doing this through a constructor but upon using this method for the Windows Documentation, I am unable to use my own constructor from my knowledge. How would you folks recommend getting the link to the new window?
There are many ways.
the simplest, though not necessarily the most elegant, is to create a new class that inherits from Frameand add to it a property for the link. Something like this:
public class FooFrame: Frame
{
public string Link;
}
then in your code assign it upon initializing the frame:
FooFrame frame = new FooFrame(){Link = "youtube.link.com"}

Close secondary view completely in UWP

I am using a secondary view to run my media files, but When I close my secondary view with close button on it (while media is still playing) the secondary view/window closes but the media somehow keeps playing because I can hear the sound and source of sound seems to be the primary view (main app window). How can I completely terminate the secondary window when I close it?
Here is my code to create the secondary view.
await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
var frame = new Frame();
frame.MinHeight = 200;
frame.MinWidth = 200;
compactViewId = ApplicationView.GetForCurrentView().Id;
frame.Navigate(typeof(CompactNowPlayingPage), caption);
Window.Current.Content = frame;
Window.Current.Activate();
ApplicationView.GetForCurrentView().Title = Title;
});
bool viewShown = await ApplicationViewSwitcher.TryShowAsViewModeAsync(compactViewId, ApplicationViewMode.Default);
Update
After some debugging I've come to know that close button pressed on the secondary view only hides the view but it keeps on running on its thread, I just want that close button to completely close the secondary view, close its thread and destroy the window as a whole.
Update 2
I followed windows samples multiple views and was able to complete all steps, the code runs fine until it reaches Windows.Current.Close() in released event.
Then it gives an exception when it tries "Window.Current.Close()" with in the released event. according to documentation exception occurs due to any on going changes ( which might be because of media file playing ), but I need to force close the window even when media file is playing how can I do that? Here is the exception:
Message = "COM object that has been separated from its underlying RCW cannot be used."
Update 3
This is the latest updated, I am not following official sample now, just following simpler approach now.
Code to open secondary view:
await Helpers.DeviceTypeHelper.CompactOpen(e.ClickedItem as Video, identifier); //where identified is just a string for some custom logic in the secondary view.
//following method is located in a helper class within the project
internal static async Task CompactOpen(Video PlayingVideo, string caption)
{
ApplicationView newView = null;
await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
var frame = new Frame();
frame.Navigate(typeof(CompactNowPlayingPage),new object[] { PlayingVideo,caption});
Window.Current.Content = frame;
Window.Current.Activate();
newView = ApplicationView.GetForCurrentView();
newView.Title = PlayingVideo.MyVideoFile.DisplayName;
});
await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newView.Id);
}
Secondary View:
public sealed partial class CompactNowPlayingPage : Page
{
public CompactNowPlayingViewModel ViewModel { get; } = new CompactNowPlayingViewModel();
private CustomMediaTransportControls controls;
public CompactNowPlayingPage()
{
InitializeComponent();
this.Loaded += MediaPage_Loaded;
this.Unloaded += MediaPage_Unloaded;
Microsoft.Toolkit.Uwp.UI.Extensions.ApplicationView.SetExtendViewIntoTitleBar(this, true);
Microsoft.Toolkit.Uwp.UI.Extensions.TitleBar.SetButtonBackgroundColor(this, Colors.Transparent);
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
string chk = "";
var paramm = e.Parameter as object[];
NowPlayingVideo = paramm[0] as Video;
var vis = Visibility.Collapsed;
chk = paramm[1].ToString();
switch (chk)
{
case "library":
vis = Visibility.Visible;
break;
case "playlist":
vis = Visibility.Visible;
break;
case "history":
vis = Visibility.Collapsed;
break;
case "directplay":
vis = Visibility.Collapsed;
break;
default:
break;
}
controls = new CustomMediaTransportControls(NowPlayingVideo,vis);
Media.TransportControls = controls;
PlayVideo();
}
private Video NowPlayingVideo { get; set; }
private void PlayVideo()
{
if (NowPlayingVideo != null)
{
string token = "";
if (StorageApplicationPermissions.FutureAccessList.Entries.Count == 800)
{
var en = StorageApplicationPermissions.FutureAccessList.Entries;
StorageApplicationPermissions.FutureAccessList.Remove(en.Last().Token);
}
token = StorageApplicationPermissions.FutureAccessList.Add(NowPlayingVideo.MyVideoFile);
Media.Source = null;
Media.Source = $"winrt://{token}";
SetViews();
}
}
private void SetViews()
{
NowPlayingVideo.Views++;
Database.DbHelper.UpdateViews(NowPlayingVideo.MyVideoFile.Path);
}
private void MediaPage_Loaded(object sender, RoutedEventArgs e)
{
Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().Consolidated += MediaPage_Consolidated;
}
private void MediaPage_Unloaded(object sender, RoutedEventArgs e)
{
Windows.UI.ViewManagement.ApplicationView.GetForCurrentView().Consolidated -= MediaPage_Consolidated;
}
private void MediaPage_Consolidated(Windows.UI.ViewManagement.ApplicationView sender, Windows.UI.ViewManagement.ApplicationViewConsolidatedEventArgs args)
{
Window.Current.Close();
}
}
Secondary View XAML:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<vlc:MediaElement AreTransportControlsEnabled="True"
Name="Media"
HardwareAcceleration="True"
AutoPlay="True">
</vlc:MediaElement>
</Grid>
Case 1 : Everything runs perfect if I place a video file in Assets folder and give it as a source to the media element and comment the whole OnanvigatdTo method on secondary page. And I am able to successfully close the window as well.
...
Case 2 : But when I try to set the media through the NowPlayingVideo object as shown in the code above and I also use default Transport Controls, so I don't comment the lines used to assign custom transport controls in the above code it runs fine, but when I then try to close the window I get following exception in App.i.g.cs file but stacktrace doesn't exist:
Message = "Attempt has been made to use a COM object that does not have a backing class factory." Message = "COM object that has been separated from its underlying RCW cannot be used.
Case 3 : Exactly like case 2 but here I uncomment Custom transport controls lines so now I am assigning custom transport controls to my media element, this time exception is a bit different with some stacktrace as well
StackTrace = " at System.StubHelpers.StubHelpers.GetCOMIPFromRCW_WinRT(Object objSrc, IntPtr pCPCMD, IntPtr& ppTarget)\r\n at Windows.UI.Xaml.DependencyObject.get_Dispatcher()\r\n at VLC.MediaElement.d__160.MoveNext()\r\n--- End of stack trace ...
Message = "Attempt has been made to use a COM object that does not have a backing class factory."
The short answer is: you need to make sure nothings holds on to your view instance, and you call Window.Close in the view's Consolidated event. The longer answer with code is here in the official sample. Take a look at the ViewLifetimeControl.cs source file: https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/MultipleViews/cs

C# UWP closing application multiple views

I have a UWP app that dynamically creates another view. But, the problem is when I close the first window, the second still stays on.
With this code I am creating new view:
CoreApplicationView newView = CoreApplication.CreateNewView();
int newViewId = 0;
await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Frame frame = new Frame();
frame.Navigate(typeof(SecondPage), null);
Window.Current.Content = frame;
// You have to activate the window in order to show it later.
Window.Current.Activate();
newViewId = ApplicationView.GetForCurrentView().Id;
});
bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
How to close the application when user close the first window?
Please refer to Show multiple views for an app.
If secondary views are open, the main view’s window can be hidden – for example, by clicking the close (x) button in the window title bar - but its thread remains active. Calling Close on the main view’s Window causes an InvalidOperationException to occur. (Use Application.Exit to close your app.) If the main view’s thread is terminated, the app closes.
private void Btn_Click(object sender, RoutedEventArgs e)
{
Application.Current.Exit();
}
You could also choose if you want to close the initial window and remove it from the taskbar by specifying the value of ApplicationViewSwitchingOptions.So that there is a single view on your desktop. You just need to close it for closing the application.
private int currentViewId = ApplicationView.GetForCurrentView().Id;
private async void ShowNewView()
{
CoreApplicationView newView = CoreApplication.CreateNewView();
int newViewId = 0;
await newView.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
Frame frame = new Frame();
frame.Navigate(typeof(SecondPage), null);
Window.Current.Content = frame;
Window.Current.Activate();
newViewId = ApplicationView.GetForCurrentView().Id;
});
await ApplicationViewSwitcher.SwitchAsync(newViewId, currentViewId, ApplicationViewSwitchingOptions.ConsolidateViews);
}

Categories