WPF Progress bar not updating while calling the method from another class - c#

Trying to call the mainwindow update progressbar method from another class. The progressbar is not getting updated.
XAML Code
<ProgressBar x:Name="ProgressSplit" Margin="0,0,0,0" HorizontalAlignment="Center" VerticalAlignment="Center"
Grid.Row="11" Grid.Column="2" Minimum="0" Maximum="100" Foreground="#ffe600"
Width="700" Height="30" />
<TextBlock x:Name="txtProgressText" Text="{Binding ElementName=ProgressSplit, Path=Value, StringFormat={}{0:0}%}" Grid.Row="11" Grid.Column="2"
HorizontalAlignment="Center" VerticalAlignment="Center" />
MainWindow.xaml code
private delegate void UpdateProgressBarDelegate(DependencyProperty dp, Object value);
public void UpdateProgressBar(double value)
{
var updatePbDelegate = new UpdateProgressBarDelegate(ProgressSplit.SetValue);
Dispatcher.CurrentDispatcher.Invoke(updatePbDelegate, DispatcherPriority.Background, new object[] { RangeBase.ValueProperty, value });
}
Trying to call the public method from another class to update the progress bar
var mainWindow = (MainWindow)Application.Current.MainWindow;
mainWindow.UpdateProgressBar(20);

I'm not sure what the specific problem with your code is, but that is not the recommended way to handle progress. One problem is that you rely on a public property of the main window, so you have a tight coupling between your UI and your worker method.
Typically you should do something like
public async void OnButtonpress(){
var progress = new Progress<int>(Update);
void Update(int value) => ProgressSplit.Value = value;
try{
await Task.Run(() => WorkerMethod(progress));
}
catch{
...
}
}
public void WorkerMethod(IProgress<int> progress){
// on background thread
for(int i = 0; i< 100; i++){
// do work
progress.Report(i);
}
}
The progress class will take care of updating the progress bar on the UI thread. This helps decouple the UI from the worker method. This pattern is also amenable to adding things like cancellation etc. Just remember to take care to set the maximal value of the progress bar. Personally I prefer to always use a double to report progress, where 1.0 represent a completed task, so you can always report i / totalNumberOfItems.

Related

In WPF when I try to insert some text in a keyboard textbox the textbox does not update

My application in WPF has to manage a series of windows. The main window manages the display of data acquired in real time. There are also other windows that allow you to tune parameters that change the displayed data. One of these windows consists of a slider and a textBox. The slide and the textBox bind to each other so that one updates the other. When the display is present, however, the textbox is not responsive while the slider is and this causes me a strong slowdown in the insertion of any parameters to tune.
In xaml:
<Slider Name="TAZSlider"
Grid.Column="0" Grid.Row="1"
Background="{x:Null}" mah:SliderHelper.EnableMouseWheel="MouseHover"
Orientation="Vertical"
Value="{Binding TAZValue}"
mah:SliderHelper.ThumbFillBrush="#FF9900"
mah:SliderHelper.ThumbFillHoverBrush="#faca82"
mah:SliderHelper.TrackValueFillBrush="#FF9900"
mah:SliderHelper.TrackValueFillPressedBrush="#FF9900"
mah:SliderHelper.TrackValueFillHoverBrush="#ff9900"
Maximum="{Binding TAZMax}"
Minimum="{Binding TAZMin}"
SmallChange="1"
IsSnapToTickEnabled="True"
ValueChanged="TAZSlider_ValueChanged"
AutoToolTipPlacement="TopLeft"
AutoToolTipPrecision="1"
TickFrequency="1"
ScrollViewer.PanningRatio="5"
ToolTipService.ToolTip="{Binding Path=Value, ElementName=TAZSlider}" ></Slider>
<TextBox Name="TAZTextBox" Grid.Column="0" Grid.Row="3" HorizontalAlignment="Center" Height="50" TextWrapping="Wrap" VerticalAlignment="Center" Width="100"
FontFamily="Segoe UI Symbol" FontSize="25" FontWeight="UltraBold" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Background="#FF707070" Foreground="#ececec"
Text="{Binding ElementName=TAZSlider, Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" KeyDown="TAZTextBox_KeyDown" />
C#:
public int TAZMin
{
get { return this._mainParent.tazMinimLimits;}
set { this._mainParent.tazMinimLimits = value; }
}
public int TAZMax
{
get { return this._mainParent.tazMaximumLimits; }
set { this._mainParent.tazMaximumLimits = value; }
}
public int TAZValue
{
get { return this._mainParent.tazValue; }
set { this._mainParent.tazValue = value; }
}
private void TAZSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (!isFistStart)
{
System.Diagnostics.Debug.WriteLine("[DEBUG] : Set new value (int)TAZSlider.Value " + (int)TAZSlider.Value);
Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render, new Action(() =>
{
this._mainParent.sinaps.dataManagerTools.DAQControl.SetAZ_Period((int)TAZSlider.Value);
}));
}
}
private void TAZTextBox_KeyDown(object sender, KeyEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Debug TAZTextBox_KeyDown :" + e.Key);
Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Input, new Action(() => { TAZTextBox.Text = TAZTextBox.Text + "!"; }));
}
Without display everything works without having to associate the "TAZTextBox_KeyDown" event while with the display it does not. I tried to insert Dispatcher.Invoke (System.Windows.Threading.DispatcherPriority.Input, new Action (() => {TAZTextBox.Text = TAZTextBox.Text + "!";})) and that makes it distinctly responsive but sometimes it works (randomly) the keyboard entry works and therefore this causes me difficulties. Can anyone explain to me if there is a specific property of the textbox that makes keyboard input immediately responsive?

What are the implications of creating WPF controls on background thread?

So, lets say I have STA thread running on background and I create a user control there.
How functional it is going to be? What are the limitations?
_workingThread = new Thread(() =>
{
//so far so good
var myControl = new MyCustomControl();
//what happens if i set DataContext? Will databinding work?
//It looks like it does, but I am not entirely sure.
myControl.DataContext = new MyViewModel();
//if databinding works, can I assume that at this point
//myControl's properties are already updated?
//what happens exactly if I invoke a delgate using Dispatcher property?
myControl.Dispatcher.Invoke(SomeMethod);
//or current dispatcher?
Dispatcher.CurrentDispatcher.BeginInvoke(SomeOtherMethod);
});
_workingThread.SetApartmentState(ApartmentState.STA);
_workingThread.Start();
To answer the question why: there is a component in .Net called XpsDocument which allows you to write visuals into xps file. I don't see a reason, why I should do it on UI thread.
Here is example of WPF app, which creates Window in new STA Thread. I don't see any problem with it. I printed out some things: Thread name, ThreadId and Counter (changes via INotifyPropertyChanged). Also I change stackPanelCounter's background from timer Dispatcher.BeginInvoke.
XAML:
<Window x:Class="WpfWindowInAnotherThread.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen" Title="WPF: Windows and Threads">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<TextBlock Text="{Binding ThreadId, StringFormat='ThreadId: {0}'}" />
<TextBlock Text="{Binding ThreadName, StringFormat='ThreadName: {0}'}" />
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" Name="stackPanelCounter">
<TextBlock Text="Counter: " />
<TextBlock Text="{Binding Counter}" />
</StackPanel>
<StackPanel Grid.Row="2">
<Button Name="btnStartInNewThread" Content="Start window in new Thread"
Click="btnStartInNewThread_Click"/>
<Button Name="btnStartTheSameThread"
Content="Start window in the same Thread"
Click="btnStartTheSameThread_Click" />
</StackPanel>
</Grid>
</Window>
Code:
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
namespace WpfWindowInAnotherThread
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
static int _threadNumber = 0;
readonly Timer _timer;
int _Counter;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public int ThreadId
{
get { return Thread.CurrentThread.ManagedThreadId; }
}
public string ThreadName
{
get { return Thread.CurrentThread.Name; }
}
public int Counter
{
get { return _Counter; }
set { _Counter = value; PropertyChanged(this, new PropertyChangedEventArgs("Counter")); }
}
public MainWindow()
{
DataContext = this;
_timer = new Timer((o) => {
Counter++;
MainWindow wnd = o as MainWindow;
wnd.Dispatcher.BeginInvoke(new Action<MainWindow>(ChangeStackPanelBackground), wnd);
}, this, 0, 200);
InitializeComponent();
}
private void btnStartTheSameThread_Click(object sender, RoutedEventArgs e)
{
MainWindow mainWnd = new MainWindow();
mainWnd.Show();
}
private void btnStartInNewThread_Click(object sender, RoutedEventArgs e)
{
Thread thread = new Thread(new ThreadStart(ThreadMethod));
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
}
private static void ThreadMethod()
{
Thread.CurrentThread.Name = "MainWindowThread# " + _threadNumber.ToString();
Interlocked.Increment(ref _threadNumber);
MainWindow mainWnd = new MainWindow();
mainWnd.Show();
Dispatcher.Run();
}
private static void ChangeStackPanelBackground(MainWindow wnd)
{
Random rnd = new Random(Environment.TickCount);
byte[] rgb = new byte[3];
rnd.NextBytes(rgb);
wnd.stackPanelCounter.Background = new SolidColorBrush(Color.FromArgb(0xFF, rgb[0], rgb[1], rgb[2]));
}
}
}
I spent some time testing things out, and I think Clemens's comment was accurate. Key points are:
myControl.Dispatcher and Dispatcher.CurrentDispatcher are one and the same, both hold a reference to dispatcher of background thread. No surprises here.
In general controls will not behave correctly without dispatcher running, because Dispatcher.BeginInvoke calls will not be processed. You have two options. Either call Dispatcher.Run() on background thread and create your controls using invokes:
_backgroundDispatcher.BeginInvoke(new Action(() =>
{
var myControl = new MyCustomControl();
//do stuff
}));
or manually push dispatcher frame every time you want to process dispatcher queue and "refresh" your control. When it comes to building XPS pages, both approaches are viable.
Data bindings do work, even when control is created on background thread. However in some cases they are not applied instantly and you might have to wait for dispatcher to process it's queue.

synchronously show and hide controls in windows phone 8

In my windows phone 8 app I want to show a message before loading some items from database. After loading I want to hide the message again.
for examle my XAMLcode is like this:
<Grid Name="layoutRoot">
<Button Content="load more..."
Click="loadmore_clicked"/>
<TextBlock Name="loadingMessage"
Visibility="Collapsed"
Text="loading..." />
<phone:LongListSelector ItemsSource="{Binding Students}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<TextBlock Margin="10"
Text="{Binding name}" />
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
and in my c# code I am trying something like this:
private void loadmore_clicked(object sender, RoutedEventArgs e)
{
loadingMessage.Visibility = Visibility.Visible;
LoadMoreItemsForLongList(); // loading more items for the LongListSelector's ItemSource
loadingMessage.Visibility = Visibility.Collapsed;
}
The "loadingMessage" TextBlock never been shown. For testing I put Thread.Sleep() in the LoadMoreItemsForLongList() function. But it didn't work either.
My goal is to show a "loadingMessage" TextBlock before start loading more data to the LongListSelector and hide the "loadingMessage" TextBlock after finishing load of more data. How can I do that? any help will be appreciated.
Seems like that LoadMoreItemsForLongList is a blocking method. When visibility of the loadingMessage is set, it's visual apprearance won't get updated until control goes of the loadmore_clicked (which is running in UI thread) and UI can show the loadingMessage, but before that, it's Visibility set to Collapsed.
You run the LoadMoreItemsForLongList in another thread:
private void loadmore_clicked(object sender, RoutedEventArgs e)
{
loadingMessage.Visibility = Visibility.Visible;
Task.Run( () =>
LoadMoreItemsForLongList() // loading more items for the LongListSelector's ItemSource
).ContinueWith( () =>
loadingMessage.Visibility = Visibility.Collapsed;
);
}
But you need to be aware that all UI interactions in the LoadMoreItemsForLongList need to done in UI Thread. You may use Dispatcher:
void LoadMoreItemsForLongList() {
// Load list from DB
CoreWindow.GetForCurrentThread().Dispatcher.Current.RunAsync(
CoreDispatcherPriority.Normal, () => {
this.lstItems.ItemsSouce = ...;
});
}
But if you follow async design patterns and define LoadMoreItemsForLongList async, you can do this instead:
private async void loadmore_clicked(object sender, RoutedEventArgs e)
{
loadingMessage.Visibility = Visibility.Visible;
await LoadMoreItemsForLongListAsync();
loadingMessage.Visibility = Visibility.Collapsed;
}
This is the prefered way. and again, you still need to perfom UI actions of LoadMoreItemsForLongListAsync in UI thread.

Problems with binding values

I'm having a problem with binding values to controls. I checked in other windows I have and I think I'm doing it the same way.
I want to show something like loading window from App before main window will open. The problem is nothing is changing (text/content/value of controls).
InitizalizationWindow:
public InitializationWindow()
{
...
InitializationWindowClass.Progress = new InitializationWindowClass();
this.mainSP.DataContext = InitializationWindowClass.Progress;
}
and part of xaml:
<StackPanel Name="mainSP" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="0, 0, 0, 10">
<TextBlock x:Name="tblProgress" FontSize="14" Text="{Binding ProgressText}" TextAlignment="Center" TextWrapping="Wrap" />
<Grid>
<telerik:RadProgressBar x:Name="progress" Value="{Binding ProgressValue}" telerik:StyleManager.Theme="Summer" Height="25" IsIndeterminate="False" />
<Label x:Name="lblPercent" FontWeight="Bold" Content="{Binding ProgressValueString}" HorizontalAlignment="Center" VerticalContentAlignment="Center" />
</Grid>
</StackPanel>
InitializationWindowClass:
public class InitializationWindowClass : INotifyPropertyChanged
{
public static InitializationWindowClass Progress { get; set; }
private string progressText = String.Empty, progressValueString = String.Empty;
private int progressValue = 0;
public event PropertyChangedEventHandler PropertyChanged;
public string ProgressText
{
get
{
return progressText;
}
set
{
progressText = value;
NotifyPropertyChanged("ProgressText");
}
}
public string ProgressValueString
{
get
{
return progressValueString;
}
set
{
progressValueString = value;
NotifyPropertyChanged("ProgressValueString");
}
}
public int ProgressValue
{
get
{
return progressValue;
}
set
{
progressValue = value;
ProgressValueString = String.Format("{0}%", progressValue);
NotifyPropertyChanged("ProgressValue");
NotifyPropertyChanged("ProgressValueString");
}
}
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and part of App.xaml.cs:
protected override void OnStartup(StartupEventArgs e)
{
InitializationWindow iw = new InitializationWindow();
iw.Show();
InitializationWindowClass.Progress.ProgressValue = Convert.ToInt32(((decimal)count / (decimal)sum) * 100);
InitializationWindowClass.Progress.ProgressText = "Some text";
...
...
InitializationWindowClass.Progress.ProgressValue = Convert.ToInt32(((decimal)count / (decimal)sum) * 100);
InitializationWindowClass.Progress.ProgressText = "New text";
...
...
}
I checked and when I'm changing i.e. ProgressValue from App.xaml.cs, the value is changing - going to
get
{
return progressValue;
}
So the question is - what I'm doing wrong?
This is a threading issue. Binding changes are posted to the Dispatcher, and then the UI thread pulls those events out of the Dispatcher queue and processes them.
Here you are making changes to the bindings in the OnStartup event, which itself is being handled by the UI thread. This means that those binding changes won't be handled until the OnStartup event is exited. What you probably end-up seeing in your user interface is just the values of those progress properties when they were last changed.
What you need to do is move the progress reporting to another thread. In other words, shift the work currently done in the OnStartup event to a different thread, using async/await or some other threading mechanism.
Original, incorrect answer follows:
ProgressText, ProgressValueString and ProgressValue are all properties on InitializationWindowClass, not InitializationWindow. It looks like you need to either set the DataContext on the StackPanel to point at the Progress property of the InitializationWindow, or include the Progress property as part of the binding path for the individual elements.
That is, either this:
<StackPanel ... DataContext="{Binding Progress}">
... rest the same ...
</StackPanel>
or this:
<StackPanel ...>
<TextBlock ... Text="{Binding Progress.ProgressText}" ... />
<Grid>
<telerik:RadProgressBar ... Value="{Binding Progress.ProgressValue}" ... />
<Label ... Content="{Binding Progress.ProgressValueString}" ... />
</Grid>
</StackPanel>
What you are doing won't work. You are displaying your splash screen and performing load operations on the same thread. This way, while your loading operations run, the main UI thread is busy and is not updating, even if the data in the background are changing properly. That's why you are probably seing "wait" cursor, your splash window is unresponsive, and your main window may not even appear at all.
SplashScreen should be displayied on different thread
You have to have a way of communicating with that thread, because:
you can't directly update UI from one thread using another thread
Here you have a nice example of working SplashScreen in wpf

using the dispatcher to load usercontrols in WPF

I have a user control that has a grid with like 2900 items in it, there is nothing I can do about this cause that is the way the business want's it... Obviously this is slow to load/render so I created this trick using the dispatcher, in the view model that handles the event (prism event... not a standard windows event).
public void ShowPopUp(Type viewType)
{
var waitScreen = new Controls.Views.SampleView();
var popUp = new ShellBlank();
popUp.Content = waitScreen;
popUp.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
popUp.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background, new Action(delegate() {
popUp.Content = container.Resolve(viewType);})
);
popUp.ShowDialog();
}
It works just fine however on my SampleView (as it is called at the moment) there is an in-determinant progress bar however it never updates, like you know - the green bara going back and fourth... Here is the XAML for it.
<Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Margin="12" FontSize="16" Foreground="WhiteSmoke" Content="Loading... Please wait"/>
<ProgressBar Grid.Row="1" IsIndeterminate="True" Width="280" Height="24"/>
</Grid>
</Border>
Is it something to do with the dispatcher not letting it update?
Anyone ever done something like this? got any suggestions?
Thanks!
My guess is that the Dispatcher thread is busy trying to render your control and hasn't been able to update the ProgressBar.
Is the popup window responsive? Try moving the window or adding a button and seeing if you can click on the button. This might help identify whether its a simple problem with the progress bar or the Dispatcher being too busy.
Actually I ended up doing was calling container.resolve on the view, I called called container.resolve on the view model, this could be done with a standard background worker - then in the RunWorkerCompleted with happens bank on the main thread I create the view passing in the view model that we happily waited for in the background and switch out the wait screen which of course wasn't impacted by our long running process. Here is the code.
AddVendorModalityViewModel viewModel;
var waitScreen = new Controls.Views.SampleView();
var popUp = new ShellBlank();
popUp.Content = waitScreen;
popUp.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
var bw = new BackgroundWorker() { WorkerReportsProgress = true, WorkerSupportsCancellation = true };
bw.DoWork += (s, e) =>
{
viewModel = container.Resolve<AddVendorModalityViewModel>();
e.Result = viewModel;
};
bw.RunWorkerCompleted += (s, e) =>
{
viewModel = (AddVendorModalityViewModel)e.Result;
AddVendorModalityView view = new AddVendorModalityView(viewModel);
popUp.Content = view;
};
bw.RunWorkerAsync();
popUp.ShowDialog();
Hope this is useful for someone... The general idea is create the ViewModel on a different thread cause it is the thing that will take forever to load etc...

Categories

Resources