Changing Text On Button Click, Then Again After Runtime - c#

I am writing a program that corrects values in text files. In my xaml I have a textbox that is supposed to report the status/progress. "waiting"=before program starts, "processing"= program is processing files, "done"= files have been processed.
Here is my xaml code for the text field and the RUN button:
<TextBox x:Name="statusBox" HorizontalAlignment="Left" Height="23" Margin="130,332,0,0" TextWrapping="Wrap" Text="waiting" VerticalAlignment="Top" Width="120" FontSize="14" TextAlignment="Center" IsReadOnlyCaretVisible="True" >
<Button Content="Run"
Name="Run"
HorizontalAlignment="Left"
Margin="562,27,0,0"
VerticalAlignment="Top"
Width="53"
Click="Run_Click"
RenderTransformOrigin="-0.305,0.487" Height="19">
<Button.BindingGroup>
<BindingGroup Name="btnOpen"/>
</Button.BindingGroup>
<Button.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-0.848"/>
<TranslateTransform/>
</TransformGroup>
</Button.RenderTransform>
</Button>
Here is the problem I am having:
I have tried this many different ways. What I have below is the most recent attempt.
//In XAML, the status defaults to "waiting".
public class Operate
{
public void operations()
{
statusBox.Text = "processing";
... Perform Text Operations
status.Text = "finished";
}
}
When I run the program, I get see "waiting" in the status text box. However, when I click Run, the program begins but the text still says "waiting". Then, when the program has finished running, the text changes to "finished" without changing to "processing" in between.
Any help is greatly appreciated. I have programmed many console applications, but am relatively new to wpf.
Thanks.

When you start running, your UI won't be updated until it ends running the current thread.
After completing the task, your status will be updated very fast from processing to finished, so you won't see the change.
I recommend using a background worker to perform longer operations and still have the UI operational.
See this howto: Use a background worker

You may want to take a look into using a BackgroundWorker. It appears the UI is being blocked while processing your requests.
MSDN BackgroundWorker Class

As others have said the problem is that all your code is executing on the same thread. Your operations are running on the UI thread, which means it cannot respond to things like changes as you are trying to do.
The way around this is move your work onto a new thread.
(This assumes a newer version of the .NET framework that supports async/await syntax)
public class Operate
{
public async void operations()
{
statusBox.Text = "processing";
await Task.Run(()=>{
//do work here
});
status.Text = "finished";
}
}
Keep in mind that since that Task.Run command executes on a different thread it cannot access the UI. If you need it to have some information from the UI, you can pass it in the Task.Run call like so:
await Task.Run(mydata=>{
//do stuff, with mydata variable
});
If you need to return something to display on the UI then you can do this:
var myReturnValue = await Task.Run(mydata=>{
//do work
return myNewReturnData;
});
//can do something with your return data here.
If you need the ability for the user to click a different button to cancel the transaction look up the CancellationToken class.

Related

Why do WPF tabs become "unresponsive" when `Dispatcher.Invoke()` is called?

As I understand it, WPF "messages" (e.g. a button click handler) are added to an internal prioritized queue. A single UI thread is then responsible for processing the queued messages.
Unfortunately my knowledge of WPF is not deep enough to understand the internal working of the framework. So my question is, given that there is only 1 thread processing messages...
What is the internal sequence of events (high level) that is leading to the tabs becoming unresponsive?
Observed Behavior
If you click slowly, the TabControl behaves as expected.
To reproduce: click 1 tab every 4 seconds.
It appears that if you give the TabControl.SelectedIndex data binding an opportunity to complete, the control will behave as designed.
If you click tabs quickly, then some of the tabs will become unresponsive.
To reproduce: click as many tabs as you can within 3 seconds.
Additional Reading
Two selected tabs in tabcontroller
While the behavior is similar, this article is different because the symptom is the result of using a Tab + MessageBox.
Sample Code
The following code can be used to reproduce the behavior, whereby, WPF tabs become permanently selected.
Paste into MainWindow.xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="30" />
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<WrapPanel Grid.Row="0">
<TextBlock>
1. Click on as many tabs as possible within 3 seconds.<LineBreak/>
2. Wait until multiple tabs are selected.<LineBreak/>
3. Uncheck the `Simulate Bug` checkbox.<LineBreak/>
</TextBlock>
</WrapPanel>
<CheckBox Grid.Row="1" IsChecked="{Binding CanSimulateBug}" Content="Simulate Bug"/>
<TabControl x:Name="ColorWorkspaces" Grid.Row="2" SelectedIndex="{Binding SelectedTab, Mode=TwoWay}">
<TabItem x:Name="RedTab" Header="Red"/>
<TabItem x:Name="OrangeTab" Header="Orange"/>
<TabItem x:Name="YellowTab" Header="Yellow"/>
<TabItem x:Name="GreenTab" Header="Green"/>
<TabItem x:Name="BlueTab" Header="Blue"/>
<TabItem x:Name="VioletTab" Header="Violet"/>
</TabControl>
<TextBlock Grid.Row="3" Text="{Binding Status}"/>
</Grid>
Paste into MainWindow.xaml.cs:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private int _selectedTab;
private string _status;
private bool _canSimulateBug;
public MainWindow()
{
this.CanSimulateBug = true;
this.Status = String.Empty;
this.DataContext = this;
InitializeComponent();
}
protected void RaisePropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
public bool CanSimulateBug
{
get
{
return _canSimulateBug;
}
set
{
_canSimulateBug = value;
RaisePropertyChanged();
}
}
public string Status
{
get
{
return _status;
}
set
{
_status = value ?? string.Empty;
RaisePropertyChanged();
}
}
public int SelectedTab
{
get
{
return _selectedTab;
}
set
{
UpdateStatus($"SelectedTab changing... Value={value}");
if (this.CanSimulateBug)
{
SimulateBug(value);
}
_selectedTab = value;
UpdateStatus($"SelectedTab changed. Value={value}");
// This missing line was added as per
Felix's comment
RaisePropertyChanged();
}
}
private void UpdateStatus(string message)
{
var formattedMessage = $"[{Thread.CurrentThread.ManagedThreadId}] {DateTime.Now.ToLongTimeString()}: {message}";
this.Status = formattedMessage;
Debug.WriteLine(formattedMessage);
}
private void SimulateBug(int id)
{
var delay = TimeSpan.FromSeconds(3);
UpdateStatus($"Bug simulation started... ID={id}, Delay={delay}");
// IMPORTANT: If you comment out this following line
// ... the application will behave as expected.
Application.Current.Dispatcher.Invoke( // blocking call
DispatcherPriority.Background, // tells UI thread to execute this as lowest priority job
new Action(delegate { /* do nothing */ }));
Thread.Sleep(delay);
UpdateStatus($"Bug simulation complete. ID={id}");
}
}
Your complete code is executing on a single thread. You can't execute concurrent operations using a single thread. What you are currently doing is to block the main thread twice (too long) by invoking two potentially long-running operations synchronously:
Synchronous Dispatcher invocation using Dispatcher.Invoke:
Application.Current.Dispatcher.Invoke(() => {}, DispatcherPriority.Background);
Synchronous thread sleep:
Thread.Sleep(TimeSpan.FromSeconds(3));
While executing these synchronous operations, the main thread is not free to execute the logic (which is in this case part of the Selector, which is a superclass of TabControl) that manages the selection state of the hosted items.
The main thread is blocked by waiting for the Dispatcher to return and then by sending it to sleep i.e. suspending it.
As a result the Selector is not able to unselect the previously selected TabItem.
The Selector is able to cancel the selection procedure, which involves handling the selected item and unselecting every other item (in case multi-select is not supported). Obviously, the Selector cancels the unselection/processing of the pending items.
You should be able to test this by listening to the Selector.Unselected event which you attach to the TabItem. It should not be raised. Apparently the blocking of the main thread creates a race condition for the internal item validation of the Selector.
To fix this race condition it should be sufficient to increase the DispatcherPriority of the queued dispatcher messages to at least DispatcherPriority.DataBind (above DispatcherPriority.Input):
Application.Current.Dispatcher.Invoke(() => {}, DispatcherPriority.DataBind);
This is not the recommended fix, although it fixes the race condition and therefore the issue of multiple selected tabs as the critical code can now execute in time. The real underlying problem is the blocked main thread (which actually is a blocked Dispatcher).
You never want to block the main thread. Now you understand why.
For this reason .NET introduced the TPL. Additionally, the compiler/runtime environment allows true asynchronous execution: by delegating execution to the OS, .NET can use kernel level features like interrupts. This way .NET can allow the main thread to continue e.g., to process essential UI related events like device input.
Part of the interface between OS level and framework level is the Dispatcher and the InputManager. The Dispatcher basically manages the associated thread. In an STA application this is the main thread. Now, when you block the main thread using Thread.Sleep, the Dispatcher can't continue to work on the message queue that contains handlers that are executed on the associated dispatcher thread (main thread).
The Dispatcher is now unable to execute the input events posted by the InputManager. Since the dependency property system (on which routed events and the data binding engine is based on) and usually the code of UI controls are also executed on the Dispatcher, they are also suspended.
The combination of the very low dispatcher priority DispatcherPriority.Background in conjunction with the long Thread.Sleep makes the problem even worse.
The solution is to not block the main thread:
Post work to the Dispatcher asynchronously and allow the application to continue while the job is enqueued and pending by calling Dispatcher.InvokeAsync:
Application.Current.Dispatcher.InvokeAsync(() => {}, DispatcherPriority.Background);
Execute blocking I/O bound operations asynchronously using async/await:
await Task.Delay(TimeSpan.FromSeconds(3));
Execute blocking CPU bound operations concurrently:
Task.Run(() => {});
Your fixed code would look as followed:
private async Task SimulateNoBugAsync(int id)
{
var delay = TimeSpan.FromSeconds(3);
UpdateStatus($"Bug simulation started... ID={id}, Delay={delay}");
// If you need to wait for a result or for completion in general,
// await the Dispatcher.InvokeAsync call.
Application.Current.Dispatcher.InvokeAsync(() => {}, DispatcherPriority.Background);
await Task.Delay(delay);
UpdateStatus($"Bug simulation complete. ID={id}");
}

Icon not showing when asked to

My app is blocking when i want to navigate to certain view, so i want to show a load icon. The problem is the icon never shows when expected. The view changes and i never see the icon, but if i go back the icon is there.
I tried using an async task to do the navigation, but the navigation doesn't work in a task, i guess.
Any suggestions or ideas?
XAML Code:
<UserControl xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:fa="http://schemas.fontawesome.io/icons/">
<Grid>
<Listbox ItemsSource={Binding ItemsList}>
</Listbox>
<Canvas Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="-550, 180, 0, 0">
<fa:ImageAwesome Visibility="{Binding LoadingIcon, UpdateSourceTrigger=PropertyChanged}"
Icon="Spinner"
Spin="True"
Canvas.Left="56"
Canvas.Top="-17" />
</Canvas>
</Grid>
<UserControl.InputBindings>
<KeyBinding Key="Enter" Command="{Binding NavigateToMainMenuCommand}"/>
</UserControl.InputBindings>
</UserControl>
ViewModel:
public class LoginViewModel: ViewModelBase, INotifyPropertyChanged, INavigationAware
{
public InicioContableViewModel(IRegionManager regionManager,
IEventAggregator eventAggregator)
{
_regionManager = regionManager;
_eventAggregator = eventAggregator;
NavigateToMainMenuCommand = new DelegateCommand(NavigateToMainMenu);
LoadingIcon = Visibility.Hidden;
}
public DelegateCommand NavigateToMainMenuCommand { get; private set; }
private Visibility loadingIcon;
public Visibility LoadingIcon
{
get
{
return loadingIcon;
}
set
{
SetProperty(ref loadingIcon, value, nameof(LoadingIcon));
NotifyPropertyChanged(nameof(LoadingIcon));
}
}
private void NavigateToMainMenu()
{
LoadingIcon = Visibility.Visible;
string mainUri = nameof(SomeRoute.MainMenu);
_regionManager.RequestNavigate(Regions.MainRegion, mainUri);
}
}
In your Icon's properties, try to set Build Action to Resource.
WPF uses the UI thread to do things like handle mouse movement, change icons when you tell it to and to switch out one view for another. All one thread.
When that thread is busy doing something then nothing is likely to change in your UI until it's finished.
Hence, if you tell it to make something visible AND navigate, you can well find all that happens visually is the navigation. Because it has no time to show your icon.
Try making your method async and giving it a little time:
private async void NavigateToMainMenu()
{
LoadingIcon = Visibility.Visible;
await Task.Delay(60);
string mainUri = nameof(SomeRoute.MainMenu);
_regionManager.RequestNavigate(Regions.MainRegion, mainUri);
}
That task.delay should give it enough time to redraw a bit of ui.
If you need a loading icon and the thing you are navigating to is blocking then I suspect you have other problems. It's likely that whatever this new view is doing to initialise should be asynchronous. Maybe with data access etc on background threads.
You may most likely make use of the functions:- InvalidateVisual() or UpdateLayout(). Those will force to redraw and may resolve your icon visibility issues.
My app is blocking when i want to navigate to certain view, so i want to show a load icon.
If the app is blocking, you cannot show the icon, because, you know, the app is blocked. You have to remove the blocking, that is, make navigation itself quick, and do whatever initialization asynchronously. The view you're navigating to may need stuff from a database or connection to a usb device, but it won't need it immediately. You can still query the database or whatever in the background after the view is shown.
show wait indicator-> navigate -> creates view model -> starts initializion task -> initialization completes -> hide wait indicator

How to execute operations after a UserControl is fully displayed?

I have a main window that I use to display one of two possible views (ConfigView & AnalyzeView):
<Window.Resources>
<DataTemplate DataType="{x:Type vm:ConfigViewModel}">
<v:ConfigView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:AnalyzeViewModel}">
<v:AnalyzeView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding CurrentViewModel}" />
</Grid>
When transitioning from the ConfigView to the AnalyzeView, I want to first wait for the AnalyzeView to be fully displayed in the main window before performing the analysis operations. I initially added an EventTrigger for the Loaded event in the AnalyzeView as a way of starting the analysis operations:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCmd}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
However, I found that this event would be triggered before the AnalyzeView was displayed in the main window. In fact, it seems that the AnalyzeView is not displayed in the main window until sometime after the Loaded event.
Is there any event that I can trigger on so that I can start the analysis operations only after the AnalyzeView is fully displayed in the main window?
EDIT:
Ultimately, may main goal is to display a progress bar on the AnalyzeView that shows the progress of the analysis operations. Essentially, the user presses a "Start Analysis" button which transitions to the AnalyzeView and begins the analysis process, updating a progress bar as it goes.
I think what you're seeing is that the Loaded event fires after the UI thread has finished loading the control but if you have a breakpoint set in LoadedCmd, you'll notice AnalyzeView isn't rendered yet.
This is because WPF has a background rendering thread which hasn't yet had a chance to render the control, even though the UI thread is finished with it's loading. When your program hits the breakpoint, all threads are broken into and so it appears as though AnalyzeView never finished loading.
I'd suggest trying to allow the command to perform whatever operations are necessary to populate the AnalyzeView just as you've laid out here- if they're long running, and do not have to be loaded into DependencyProperties, you should run them asynchronously so that the UI thread can respond to input.
Edit 1: Following up on your comment's on #wimpSquad's answer - to keep this MVVM and show progress while keeping the UI responsive, you definitely will want to look into Tasks and reporting progress. Stephen Cleary has a good article here.
Further Reading:
WPF Threading Model
Async Loading Of Data
So, looks like you can - SO question - but I'm curious as to why you would want to. The View should only be a rendering of exposed properties in the ViewModel. What is your use case for this (morbid curiosity)?
Edit: based on your updated question, I'd recommend the following:
Load the AnalyzeView as normal, with a <ProgressBar/> bound to an exposed property that is incremented by the ViewModel (during your analysis process). This synchronicity can be achieved through event binding, and made even easier with PropertyChanged.Fody NuGet package, with the [ImplementPropertyChanged] attribute. This attribute was renamed in a more recent version of the package than what I am using, but with a quick search you should find what you are looking for there. In any case, the attribute handles triggering automatically, so as properties are updated in your ViewModel, the bound properties on your View update as well.
If you'd like to see an example of a <ProgressBar/> being used, just let me know. I've got one around here somewhere.

'MediaElement.SetSource()' isn't updating consistently

My program should play a video when the user presses the 'Play' button. While it normally does this, the very first time they press the 'Play' button nothing will happen.
I have traced this bug back to the following code, which sets my MediaElement 'VideoPlayer':
public void playVideo_Tapped(object sender, TappedRoutedEventArgs e)
{
setUpVideo();
VideoPlayer.Play();
}
public async void setUpVideo()
{
if(vm == null) return;
StorageFile videoFile = vm.videoFile;
if (videoFile == null || !videoFile.ContentType.Equals("video/mp4")) return;
using (IRandomAccessStream fileStream = await videoFile.OpenAsync(FileAccessMode.Read))
{
VideoPlayer.SetSource(fileStream, videoFile.ContentType);
}
}
The culprit seems to be the 'SetSource()' method at the end. The only variable that changes from the first click of 'Play' to the next is the variable 'VideoPlayer.PlayToSource', which is changed from null to a real value.
(As a side note, the variable 'VideoPlayer.CurrentState' also changes from 'Closed' to 'Opening' but resets itself to 'Closed' before the second click. Only 'PlayToSource' changes the functionality.)
I figured that I could do a quick-fix by doing this in my first method:
setUpVideo();
setUpVideo();
VideoPlayer.Play();
Not great code but it ought to set things straight, right? Nope! This causes a NullReferenceException. On the second call to 'setUpVideo()' I find that 'PlayToSource' still has a value and 'VideoPlayer.CurrentState' is still set to 'Opening'... which somehow triggers the NullReferenceException.
I'm expecting the solution to be one of the following things:
1.) Set 'VideoPlayer.PlayToSource' on the first click before calling 'SetSource'.
2.) In the quick-fix, set 'VideoPlayer.CurrentState' back to 'Closed' in between calls.
3.) Some other thing that mimics what the first click is doing.
Of course, both of my ideas involve changing a read-only variable. Which is where I'm getting stuck. I'll include the .xaml code for good measure, but I'm confident that it's the method 'SetSource' that's the root of my troubles:
<Grid x:Name="VideoViewerParentGrid" Background="DarkGreen" Height="{Binding VideoViewerParentGridHeight }" Width="{Binding VideoViewerParentGridWidth}">
<MediaElement x:Name="VideoPlayer" HorizontalAlignment="Center" VerticalAlignment="Bottom" Stretch="Uniform"
Visibility="{Binding VideoVisibility, Converter={StaticResource visibilityConverter}}"/>
<Button Style="{StaticResource BackButtonStyle}" Tapped="VideoViewerClose_Tapped" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Button Name="Play_Button" Content="Play Video" FontSize="26" Tapped="playVideo_Tapped"
VerticalAlignment="Top" HorizontalAlignment="Left" Height="60" Width="180" Margin="0,80,0,0"/>
</Grid>
---- UPDATE ----
Some more poking has revealed that on the first click 'VideoPlayer.CurrentState' never reaches the 'Playing' state, instead going from 'Opening' right back to 'Closed'. It does not do this on any subsequent clicks for as long as the program is running. Still investigating the cause of this.
You are missing the "await" keyword. Do this:-
await setUpVideo();
Short version, this issue is fixed by changing this:
using (IRandomAccessStream fileStream = await videoFile.OpenAsync(FileAccessMode.Read))
{
VideoPlayer.SetSource(fileStream, videoFile.ContentType);
}
...to be this:
IRandomAccessStream fileStream = await videoFile.OpenAsync(FileAccessMode.Read);
VideoPlayer.SetSource(fileStream, videoFile.ContentType);
Longer version, my code was failing because of the error "mf_media_engine_err_src_not_supported hresult - 0xc00d36c4", which was closing my MediaElement instead of playing it. This was happening because when I left the 'using' block of code the 'IRandomAccessStream' would close in the middle of my reading of the file. I'm not 100% clear why it gets through the whole thing after the first run of the code, but at least it now works reliably.
I've also got to give credit where credit is due, and I found this answer here: Windows 8 app - MediaElement not playing ".wmv" files

Functions not triggering in order

new to WPF and c# hobbyist...
For some reason, I can't get my loadingAnimation (or any other) function to run immediately after a button press and before a SOAP call.
My xaml:
<Grid>
<TextBox Height="220" HorizontalAlignment="Left" Margin="12,79,0,0" Name="txtResults" VerticalAlignment="Top" Width="337" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="12,29,0,0" Name="txtServiceTag" VerticalAlignment="Top" Width="120" />
<CheckBox Content="This computer's service tag" Height="16" HorizontalAlignment="Left" Margin="151,32,0,0" Name="chkThisST" VerticalAlignment="Top" Checked="chkThisST_Checked" Unchecked="chkThisST_Unchecked"/>
<Button Content="Get Info" Height="23" HorizontalAlignment="Left" Margin="12,324,0,0" Name="btnGetInfo" VerticalAlignment="Top" Width="75" Click="btnGetInfo_Click" />
<my:LoadingAnimation HorizontalAlignment="Center" Margin="128,154,419,127" VerticalAlignment="Center" Name="loadingAnimation" Visibility="Hidden" />
</Grid>
My .cs:
private void btnGetInfo_Click(object sender, RoutedEventArgs e)
{
txtResults.Text = "Retrieving information...";
ShowHideLoading();
SoapCall();
ShowHideLoading();
}
My SoapCall() seems to be running before txtResults.Text has time to populate. SoapCall() takes about 5 seconds to return a message. I've messed around with the order of objects in , but to no avail.
Any help is appreciated!
The reason is that the SoapCall() is blocking the UI thread. In other words, until it is finished - no UI operations will be called.
You can solve this by putting the SoapCall() inside a BackgroundWorker.
Then, the ShowHideLoading can be put inside the RunWorkerCompleted event.
Here is an example on how to use the BackgroundWorker
When you perform actions on the main thread (in this case, the thread that calls the _Click method.), the UI will not actually update until the call finishes, and the framework alerts the UI to redraw itself. The reason you're never seeing the update is because the SoapCall is blocking the main thread from performing any updates.
For this to work properly, I would recommend updating the UI, forking a new thread to perform the Soap call, then when the operation completes, reset the UI back to its original state.
You may also want to look at how to use the Dispatcher object correctly, because WPF requires that all UI updates be made on the main thread. The Dispatcher allows you to force a snippet of code to run on a particular thread.

Categories