I am developing a 'Universal App' for Windows 8.1 and Windows Phone 8.1
I need to add a ProgressRing control to my page, because there is a task which takes some time to complete and I should notify the user that the app is loading data. So I followed the instruction on MDSN in the following link:
To create an indeterminate progress ring
and they stated what I exactly need:
4. Adding an indeterminate progress ring
When you can't estimate how much work remains to finish a task and the
task does block user interaction, use an indeterminate progress ring.
An indeterminate progress ring shows an animate sequence of dots
moving in a circle.
The problem is that the ProgressRing doesn't play the animation of "loading", when I insert prog.IsActive = true; before the code that starts the task
If I comment the Task lines of code, it plays.
XAML :
<ProgressRing x:Name="prog" Margin="435,349,388,181" FontFamily="{StaticResource FontFamily6}" FontSize="25" />
C#:
private void LevelTap(object sender, TappedRoutedEventArgs e)
{
prog.IsActive = true;
CheckNav(sender);
}
private void CheckNav(object sender)
{
TextBlock tb = sender as TextBlock;
GameSetter game = new GameSetter();
game.Level = (int)TextDict[tb];
if (!LevelsArr[game.Level]) return;
if (!goToLeader)
{
prog.IsActive = false;
CallNav(true, game);
}
else
{
prog.IsActive = false;
CallNav(false, game);
}
}
private void CallNav(bool b,GameSetter g)
{
if(b) Frame.Navigate(typeof(Game8), g);
else Frame.Navigate(typeof(MainPage), g);
}
Note: The Task that runs uses Dictionary, Windows.Storage.ApplicationData, and Navigation for another page in the end.
You can use this code block in every function too if Progressing inDeterminate mode always open:
#region Progress Ring Start
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Waiter.Visibility = Visibility.Visible; // Progress ring name is Waiter.
});
#endregion
await Task.Delay(1000);
CheckNav(sender); // Your process
#region Progress Ring Finish
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Waiter.Visibility = Visibility.Collapsed; // Progress ring name is Waiter.
});
#endregion
The flaw in your code (except the fact that it's probably nearly instant anyway, as notes #kennyzx), is that you need the LevelTap method to return for the UI to be updated. And when the method returns, you've already set the ProgressRing.IsActive property back to false, which means nothing is displayed at all.
One simple way to fix that is to use the async Task.Yield method to give the UI thread a chance to run:
private async void LevelTap(object sender, TappedRoutedEventArgs e)
{
prog.IsActive = true;
await Task.Yield();
CheckNav(sender);
}
But almost nothing will be shown since your code shouldn't take long to execute. If you really want to force the indicator to be displayed for 0.5 seconds, you can do something like:
private async void LevelTap(object sender, TappedRoutedEventArgs e)
{
prog.IsActive = true;
await Task.Delay(500);
CheckNav(sender);
}
Related
I have a button that I want to disable for 3 seconds so that it's not abused. I wanted to add a Timer(3000); inside the Click event however the example code I found is using outdated method and is not working. I tried another code (which can be found below) however this throws System.Runtime.InteropServices.COMException: 'The application called an interface that was marshalled for a different thread. (0x8001010E (RPC_E_WRONG_THREAD))' error.
private void CodeButton_Click(object sender, RoutedEventArgs e)
{
CodeButton.IsEnabled = false;
var timer = new Timer(3000);
timer.Elapsed += (timer_s, timer_e) =>
{
CodeButton.IsEnabled = true;
timer.Dispose();
};
timer.Start();
Launcher.LaunchUriAsync(new Uri("https://www.hoppie.nl/acars/system/register.html"));
}
You need to use the main thread (the thread that instantiated UI components) to update UI. You get that error because the timer will work with another thread, not the main thread.
You can do it this way:
private async void Button_Click(object sender, RoutedEventArgs e)
{
try
{
// You can update the UI because
// the Click event will use the main thread.
this.Button.IsEnabled = false;
List<Task> tasks = new();
// The main thread will be released here until
// LaunchUriAsync returns.
tasks.Add(Launcher.LaunchUriAsync(new Uri("https://www.hoppie.nl/acars/system/register.html")));
tasks.Add(Task.Delay(3000));
await Task.WhenAll(tasks);
// The main thread will be back here.
}
finally
{
// This will enable the button even if you face exceptions.
this.Button.IsEnabled = true;
}
}
As I got the processes to work I ran onto possibly a Threading problem when I was trying to add a ProgressBar to give the user some clue that the process hasn't crashed. Since this is enough, an Indefinite ProgressBar in just fine for me.
<ProgressBar x:Name="ProcessProgress" Minimum="0" Maximum="100" Visibility="Hidden" IsIndeterminate="False" Width="305" Height="20" Margin="240,214,248,10"></ProgressBar>
I tried to initiate a determinate length progress bar which is not visible before the Start button is clicked :
private void Start_Click(object sender, RoutedEventArgs e)
{
...
//process starts here
var fP = new FileProcessor();
ProcessProgress.IsIndeterminate = true;
ProcessProgress.Visibility = Visibility.Visible;
...
//finally show the progressbar as full when done
ProcessProgress.IsIndeterminate = false;
ProcessProgress.Value = ProcessProgress.Maximum;
}
It just runs the whole process and my bar doesn't show up.
How can I spawn a progress bar during the process ?
The problem is that all of your code runs in the UI thread. The UI has no chance to update itself before the entire process finishes.
You can use Task.Run() to run a heavy job in the background without blocking the UI. You can use the await keyword to await that task to complete without blocking the UI. Once the task completes, you are back in the UI thread where you can modify the UI again.
A quick and dirty fix for this event handler would be :
private async void Start_Click(object sender, RoutedEventArgs e)
{
ProcessProgress.IsIndeterminate = true;
ProcessProgress.Visibility = Visibility.Visible;
...
await Task.Run(()=>
{
//process starts here
var fP = new FileProcessor();
...
});
//We are back in the UI thread, we can modify the UI
ProcessProgress.IsIndeterminate = false;
ProcessProgress.Value = ProcessProgress.Maximum;
}
No need to use Invoke to get back to the UI thread, that's the job of await itself.
A note about async void. It's ONLY meant for event handlers or similar methods. You can't await an async void method which means you can't even get any exceptions if something goes wrong. Asynchronous methods that return nothing should have the async Task signature.
If you want to report progress you can use the IProgress interface and the Progress class as explained here. The Progress class will call a callback or raise an event on the thread it was created. The payload can be any class, not just a percentage
It's best to move the reporting code to separate methods, to keep the button handler clean. The code can look like this :
//The progress class
class FileProgress
{
public string FileName{get;set;}
public int Progress{get;set;}
public string Message{get;set;}
public FileProgress(string fileName,int progress,string message)
{
FileName=filename;
Progress=progress;
Message=message;
}
}
//In the form itself
private void ReportStart()
{
ProcessProgress.IsIndeterminate = true;
ProcessProgress.Visibility = Visibility.Visible;
}
private void ReportEnd()
{
ProcessProgress.IsIndeterminate = false;
ProcessProgress.Value = ProcessProgress.Maximum;
}
private void ReportProgress(FileProgress progress)
{
ProcessProgress.Value =progress.Progress;
PanelStatus.Text = $"Working on {progress.FileName} : {progress.Message}";
}
The event handler can now look like this :
private async void Start_Click(object sender, RoutedEventArgs e)
{
ReportStart();
IProgress<FileProgress> progress=new Progress<FileProgress>(ReportProgress);
...
await Task.Run(()=>
{
//process starts here
var fP = new FileProcessor();
foreach(var file in someFiles)
{
progress.Report(new FileProgress(file,0,"Starting");
//Do some processing
progress.Report(new FileProgress(file,100,"Finished");
}
...
});
//We are back in the UI thread, we can modify the UI
ReportEnd();
}
I wonder why click event does not show me the hp value of every loop. Its only shows me hp at the start and 0 at the end
private void button_Click(object sender, RoutedEventArgs e)
{
while (player.Hp > 0)
{
int vypocet = player.Damage(player2);
player.Hp = vypocet;
label.Content = vypocet;
}
}
This should be everything you need to know
So as i said its only show me start hp and hp after whole fight and i dont know why its not show me other numbers if i am using while loop
The reason is that the event handler runs on the UI thread. This means, the changed value can be reflected in the user interface only after the whole loop ends.
If you wanted to show the progress, you would have to run the computation on another thread and use the Dispatcher to notify the UI thread about the changes.
An alternative is to yield the UI thread regularly to give the UI a chance to update. This is however not very clean.
await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);
Because UI controls will be updated after button_Click method exits.
Try change method to asynchronous and use Task.Delay which will "release" UI thread for updating controls
private async void button_Click(object sender, RoutedEventArgs e)
{
while (player.Hp > 0)
{
int vypocet = player.Damage(player2);
player.Hp = vypocet;
label.Content = vypocet;
await Task.Delay(100);
}
}
I new to WPF, and have to put a basic application together
It consists of one main window with a frame, and one page
the page has a basic status text -
the requirement is that when the page loads up, the application has to do a bunch of REST call to fetch some data from remote source, and update the status text as it fetches
problem is, as I update the text, it doesn't seem to be reflected on the page, or maybe it's being blocked - even though I've used Task
so far, I have the following code for testing:
private void Page_Loaded(object sender, RoutedEventArgs e) {
var wnd = Window.GetWindow(this);
wnd.ContentRendered += Wnd_ContentRendered;
}
private void Wnd_ContentRendered(object sender, EventArgs e) {
DisplayMessages();
}
private void DisplayMessages() {
authenticationText.Text = "text one";
var t = Task.Delay(5000);
t.Wait();
authenticationText.Text = "text two";
t = Task.Delay(5000);
t.Wait();
authenticationText.Text = "text three";
t = Task.Delay(5000);
t.Wait();
}
even though I'm waiting after each task, the UI doesn't get updated - rather it just displays text three directly after method is finished - suggestions ?
P.S: there's also a WPF loader on that page, I've noticed that it doesn't get animated as well - it seems the delay is working but everything on the UI isn't updated
I would suggest for getting the data from REST implementation , you should use the background worker and on the basis of completion of thread or progress changed you need to update the UI thread accordingly.
for getting the better insights on background worker.. kindly use this link
How to use WPF Background Worker
In your case you can use progresschanged event of the backgroundworker..
Please Create some property lets say StatusText with InotifyPropertyChanged Interface implemented and bind (use TwoWay Binding) it with the Text property of the authenticationText control .... and in the progress changed event of the backgroundworker set the value of the StatusText property,., which will automatically updates the UI.
You could try to invoke these results on the UI Thread...
Run your task normally with Task.Run or whatever. Each time you are ready to set some property on UI Thread you should invoke it through the dispatcher..
Task.Run(() =>
{
var _Temp = getSomePropTask();
Thread.Sleep(1000);
App.Current.Dispatcher.Invoke(()=>{
authenticationText.Text = _Temp;
});
});
Thanks to suggestion by Ashok, I did some background reading and have come up with the following solution using Task, async and await - which is simpler to manage than background worker threads:
private void Page_Loaded(object sender, RoutedEventArgs e) {
var wnd = Window.GetWindow(this);
wnd.ContentRendered += Wnd_ContentRendered;
}
private void Wnd_ContentRendered(object sender, EventArgs e) {
GetDataAsync();
}
private async void GetDataAsync() {
authenticationText.Text = "Connecting...";
await Task.Delay(5000);
authenticationText.Text = "Getting Member Details...";
List<MemberServiceModel> memberList = await GetMembersAsync();
// more code for handling response
}
private List<MemberServiceModel> GetMembers() {
//get all members synchronous
var request = new RestRequest("Members/Admin", Method.GET);
var response = _client.Execute<List<MemberServiceModel>>(request);
if (response.ResponseStatus != ResponseStatus.Completed) {
//TODO
_restErrorStatus = response.ResponseStatus.ToString();
_restErrorMessage = response.StatusDescription;
_logger.Error("Error in GetMembers");
_logger.Error("Status:" + _restErrorStatus);
_logger.Error("Description:" + _restErrorMessage);
}
return response.Data; ;
}
private Task<List<MemberServiceModel>> GetMembersAsync() {
//get all members asynchronous
return Task.Run(new Func<List<MemberServiceModel>>(GetMembers));
}
I have a task that runs in the form_load event of a usercontrol in winforms:
private void ucDeviceInsert_Load(object sender, EventArgs e)
{
System.Threading.Tasks.Task getTBox = System.Threading.Tasks.Task.Run(async () =>
{
await AVeryLongRunningProccess();
});
pbImage.Image = Properties.Resources.Remove;
getTBox.Wait();
pbImage.Image = Properties.Resources.Insert;
btnNext.Visible = true;
tmrDeviceInsert.Enabled = true;
tmrDeviceInsert.Start();
}
private void tmrDeviceInsert_Tick(object sender, EventArgs e)
{
Next();
}
I change the image of the picture box to inform the user the progress of the long running process. That part works fine, however the button doesn't show, and the timer never starts. I've stepped through the code, and I can confirm that it is running without any problems, which makes this even more baffling. Any ideas what would be causing this issue?
Task.Run is for pushing CPU-intensive work off the UI thread. Since you're calling an asynchronous method, I suspect it's not CPU-intensive.
So, you can just use async and await:
private async void ucDeviceInsert_Load(object sender, EventArgs e)
{
pbImage.Image = Properties.Resources.Remove;
await AVeryLongRunningProccess();
pbImage.Image = Properties.Resources.Insert;
btnNext.Visible = true;
tmrDeviceInsert.Enabled = true;
tmrDeviceInsert.Start();
}
Note that at the await, the UI is shown and the user can interact with it (that's the point).
getTBox.Wait() is going to try to complete that task synchronously. Therefore, the rest of the code after it won't happen until after the task completes.
I'd think you don't want your task to run synchronously at all, but rather handle its completion asynchronously, something like this:
getTBox.ContinueWith(() => updateStatusInUI());