I am trying to incorporate a progress bar in my main window after a button is pressed and the button is running its process. I know I am just missing something simple but I'm still new to WPF as I mainly use Windows Forms.
My XML is structured as follows:
<Window x:Class="Program1.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"
xmlns:local="clr-namespace:Program1"
mc:Ignorable="d"
Title="Program1" Height="1029" Width="1300" SnapsToDevicePixels="True" BorderThickness="0" Margin="0" ResizeMode="NoResize" Closing="Window_Closing"
x:Name="FirstWindow">
<Grid x:Name="Grid1">
<Button x:Name="btnPopulate" Content="Populate" HorizontalAlignment="Left" Margin="243,66,0,0" VerticalAlignment="Top" Width="118" Height="29" Click="btnPopulate_Click"/>
<Button x:Name="btnClear" Content="Clear" HorizontalAlignment="Left" Margin="366,66,0,0" VerticalAlignment="Top" Width="118" Height="29" Click="btnClear_Click"/>
<ProgressBar x:Name="progressBar" HorizontalAlignment="Left" Height="30" Margin="10,943,0,0" VerticalAlignment="Top" Width="351"/>
</Grid>
</Window>
I have my populate button click method as follows:
private void btnPopulate_Click(object sender, RoutedEventArgs e)
{
Thread backgroundThread = new Thread(
new ThreadStart(() =>
{
for (int n = 0; n < 100; n++)
{
Thread.Sleep(50);
progressBar.Value = n;
};
}
));
backgroundThread.Start();
}
The issue I am facing is that I am getting this error:
The name 'progressBar' does not exist in the current context
and I am unsure how I can access the progressBar control from my button click method.
I know I am likely missing something simple but I'm still trying to get the hang of WPF.
You cannot access to controls from a thread that didn't create them (old Win32 limitation). You have to use UI Sync Context to access to UI elements from background thread something like this
Somewhere in the class define field
SynchronizationContext ctx = SynchronizationContext.Current ?? new SynchronizationContext();
and then use it:
void RunOnGuiThread(Action action)
{
this.ctx.Post(o => action(), null);
}
You can also use tasks using TaskScheduler:
private readonly TaskScheduler uiSyncContext;
then define it
this.uiSyncContext = TaskScheduler.FromCurrentSynchronizationContext();
and use
var task = Task.Factory.StartNew(delegate
{
/// do something
});
this.CompleteTask(task, TaskContinuationOptions.OnlyOnRanToCompletion, delegate
{
/// do something that use UI controls
});
public void CompleteTask(Task task, TaskContinuationOptions options, Action<Task> action)
{
task.ContinueWith(delegate
{
action(task);
task.Dispose();
}, CancellationToken.None, options, this.uiSyncContext);
}
simplyfied version: You start another Thread that can't modify your UI-Thread-Content.
This solution solves it, but you still should learn about MVVM
private void btnPopulate_Click(object sender, RoutedEventArgs e)
{
SynchronizationContext context = SynchronizationContext.Current;
Thread backgroundThread = new Thread(
new ThreadStart(() =>
{
for (int n = 0; n < 100; n++)
{
Thread.Sleep(50);
context?.Post(new SendOrPostCallback((o) =>
{
progressBar.Value = n;
}), null);
};
}
));
backgroundThread.Start();
}
You should use Dispatcher.Invoke or Dispatcher.BeginInvoke methods because progressBar belongs to another thread. In other words, instead of
progressBar.Value = n;
use
Dispatcher.Invoke(new Action(()=> { progressBar.Value = n; }));
and your code should work, unless there are some typo in the names.
Please see this post for better choices in populating a ProgressBar.
Furthermore, Grid and Margin are not a good choice. Instead use DockPanel or add RowDefinitions or ColumnDefinitions to your Grid.
Where is your btnPopulate_Click() method being declared? If in the MainWindow class, then the field containing the reference to the element should exist. Please provide a good Minimal, Complete, and Verifiable code example that reliably reproduces the compile-time error message you describe.
In the meantime…
Do note that your code is otherwise entirely wrong as well. It would be best to use MVVM and simply set the progress bar state value on a view model property, binding that property to your progress bar. You also should use some other mechanism than starting a dedicated thread for dealing with background operations. I understand the code you posted is just for practice, but it's good to get into the habit of doing things the right way.
Here are some options that would be better than what you have now, and would also be better than either of the other two answers posted so far.
If dealing with a single long-running operation that has good intermittent checkpoints where you can report progress:
First, define your view model:
class ViewModel : INotifyPropertyChanged
{
private double _progressValue;
public double ProgressValue
{
get { return _progressValue; }
set { _UpdatePropertyField(ref _progressValue, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void _UpdatePropertyField<T>(
ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer.Default.Equals(field, value))
{
field = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then in your C# code for the window:
class MainWindow : Window
{
private readonly ViewModel _viewModel = new ViewModel();
public MainWindow()
{
DataContext = _viewModel;
InitializeComponent();
}
private void btnPopulate_Click(object sender, RoutedEventArgs e)
{
Task.Run(() =>
{
for (int n = 0; n < 100; n++)
{
// simulates some costly computation
Thread.Sleep(50);
// periodically, update the progress
_viewModel.ProgressValue = n;
}
});
}
}
And then in your XAML, bind the view model's ProgressValue property to the ProgressBar.Value property:
<Window x:Class="Program1.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"
xmlns:local="clr-namespace:Program1"
mc:Ignorable="d"
Title="Program1" Height="1029" Width="1300" SnapsToDevicePixels="True"
BorderThickness="0" Margin="0" ResizeMode="NoResize" Closing="Window_Closing"
x:Name="FirstWindow">
<Grid x:Name="Grid1">
<Button x:Name="btnPopulate" Content="Populate" HorizontalAlignment="Left"
Margin="243,66,0,0" VerticalAlignment="Top" Width="118" Height="29"
Click="btnPopulate_Click"/>
<Button x:Name="btnClear" Content="Clear" HorizontalAlignment="Left"
Margin="366,66,0,0" VerticalAlignment="Top" Width="118" Height="29"
Click="btnClear_Click"/>
<ProgressBar HorizontalAlignment="Left" Height="30" Margin="10,943,0,0"
VerticalAlignment="Top" Width="351" Value="{Binding ProgressValue}"/>
</Grid>
</Window>
If your long-running operation is actually made up of smaller, asynchronous operations, then you could do something like this instead:
private async void btnPopulate_Click(object sender, RoutedEventArgs e)
{
for (int n = 0; n < 100; n++)
{
// simulates one of several (e.g. 100) asynchronous operations
await Task.Delay(50);
// periodically, update the progress
_viewModel.ProgressValue = n;
}
}
Note that in this second example, you could skip the view model altogether, because the assignment for the progress value occurs in the UI thread, and so it's safe to just assign directly to the ProgressBar.Value property there. But you should still use a view model anyway, because that's more in keeping with the standard WPF paradigm and the expectations of the WPF API (i.e. you can do it the other way, but you'll be fighting the intent of the designers of the WPF API, which will lead to more frustration and difficulty).
Related
This question already has answers here:
How to implement a progress bar using the MVVM pattern
(3 answers)
Closed 1 year ago.
I'm new at programming in WPF and C# and getting used to OOP, I'm trying to code my program using the best programming practices but I have been stuck for a couple of days now and I wasn't able to figure out how to complete this, hope someone out there can help.
what I'm trying to do is very simple, I have a WPF window with a progress bar and a button, that's it, and in the C# backend code for this window, I have a click event that calls a method in another class from another file, in that class I have a BackgroundWorker and I have been trying to update a ProgressBar based on the BackgroundWorker.
I confirmed that the code works by writing the progress and events in the console, what I have not idea how to do is how to access that BackgroundWorker in the other class to update the progress bar in my xaml file, or if this is not the best approach I would like to know what would be the best way to do this
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="35"/>
<RowDefinition Height="15"/>
<RowDefinition Height="40"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<ProgressBar x:Name="progressBar"
Grid.Row = "1"
Width ="500"
Value="0"/>
<Button x:Name="btn_start"
Grid.Row = "3"
Content ="Start"
Height="30"
Width="125"
Click="btn_start_Click"/>
</Grid>
c# XAML Backend:
namespace progressBar
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btn_start_Click(object sender, RoutedEventArgs e)
{
GeneralTasks GT = new GeneralTasks();
GT.start();
}
}
}
And finally the unreadable class "GeneralTasks":
public class GeneralTasks
{
BackgroundWorker worker = new BackgroundWorker();
public void start()
{
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.WorkerReportsProgress = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerAsync();
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine(e.ProgressPercentage + "% " + (string)e.UserState);
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
worker.ReportProgress(0, string.Format("Percentage completed: 0"));
for (int i = 0; i <= 100; i++)
{
worker.ReportProgress(i, string.Format("Percentage completed: {0}", i));
Console.WriteLine("Percentage Completed: {0}",i);
Thread.Sleep(100);
}
worker.ReportProgress(100, string.Format("Done Processing"));
}
private void worker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
Console.WriteLine("Done Processing");
}
}
I'm new at programming in WPF and C# and getting used to OOP...
I advise you to use more typical modern WPF and Sharp implementations in your training.
BackgroundWorker - slightly outdated. Nowadays, Task and async/await methods are used to implement asynchrony.
The main way to get values for the properties of UI elements is to
bind to the properties of the Data Context. Bindings also solve the
problem of asynchronously changing source properties in any thread,
which cannot be done directly with UI element properties.
To call methods of the Data Context in WPF, they are wrapped in
commands. And some UI elements (including buttons, menu items) can
call these commands.
Since the main way of communicating with data is bindings, WPF
practically does not use Code Behind Windows.
Demo example of your task, but implemented in a more modern way.
ViewModel class - This is used to set the data context.
Implemented using BaseInpc and RelayCommand.
using Simplified;
using System;
using System.Threading.Tasks;
namespace ProgressView
{
public class ProgressViewModel : BaseInpc
{
private bool isProgressExecute;
private RelayCommand _startProgressCommand;
private TimeSpan _progressTime;
private double _rangeEnd;
private double _rangeBegin;
private double _currentValue;
public double RangeBegin { get => _rangeBegin; set =>Set(ref _rangeBegin, value); }
public double RangeEnd { get => _rangeEnd; set => Set(ref _rangeEnd, value); }
public TimeSpan ProgressTime { get => _progressTime; set =>Set(ref _progressTime, value); }
public double CurrentValue { get => _currentValue; private set => Set(ref _currentValue, value); }
public RelayCommand StartProgressCommand => _startProgressCommand
?? (_startProgressCommand = new RelayCommand
(
StartProgressExecuteAsync,
() => !isProgressExecute
));
private async void StartProgressExecuteAsync()
{
if (isProgressExecute)
return;
isProgressExecute = true;
StartProgressCommand.RaiseCanExecuteChanged();
double begin = RangeBegin;
double end = RangeEnd;
double range = end - begin;
TimeSpan time = ProgressTime;
DateTime beginTime = DateTime.Now;
TimeSpan elapsed;
while ((elapsed = DateTime.Now - beginTime) < time)
{
CurrentValue = begin + range * elapsed.TotalMilliseconds / time.TotalMilliseconds;
await Task.Delay(10);
}
CurrentValue = end;
isProgressExecute = false;
StartProgressCommand.RaiseCanExecuteChanged();
}
}
}
Window XAML:
<Window x:Class="ProgressView.ProgressWindow"
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"
xmlns:local="clr-namespace:ProgressView"
mc:Ignorable="d"
Title="ProgressWindow" Height="450" Width="800">
<Window.DataContext>
<local:ProgressViewModel RangeBegin="0"
RangeEnd="100"
ProgressTime="0:0:10"/>
</Window.DataContext>
<UniformGrid Columns="2">
<TextBlock Text="Begin: " VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Text="{Binding RangeBegin}" VerticalAlignment="Center"/>
<TextBlock Text="End: " VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Text="{Binding RangeEnd}" VerticalAlignment="Center"/>
<TextBlock Text="Time: " VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Text="{Binding ProgressTime}" VerticalAlignment="Center"/>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right">
<Run Text="Curent:"/>
<Run Text="{Binding CurrentValue, Mode=OneWay}"/>
</TextBlock>
<ProgressBar VerticalAlignment="Center" Height="20"
Minimum="{Binding RangeBegin}"
Maximum="{Binding RangeEnd}"
Value="{Binding CurrentValue, Mode=OneWay}"/>
<Button Content="Start Progress"
Command="{Binding StartProgressCommand, Mode=OneWay}"/>
</UniformGrid>
</Window>
I'd skip having GeneralTasks; it doesn't seem to do anything other than hold a BGW and subscribe to its events, but then doesn't do anything useful with them. I'd put the BGW in the XAML backend code and then update the ProgressBar from the ProgressChanged event (which is wired up to a method in the backend that alters the PB value) which is called when the DoWork handler (also wired up in the XAML backend file) calls ReportProgress
If you want to use the GeneralTasks class as a holder for various "methods that do things that should be done in the background" then you can basically make it a collection of methods whose signatures align with DoWork, assign one of them to be the work handler and then start the worker to get the work done
If you want to persist with GT holding the BGW you'll need to do something like expose an event that GT can raise when progress has changed or pass in a delegate that it can wire up to ProgressChanged, and have the XAML backend code pass a delegate that changes the progressbar (or more simplistically, pass the progressbar to GT)
Personally I'd just put all the BGW stuff in the XAML backend file (unless there is a huge and varied number of things it will do, in which case I'd put those things into GT and call them from the DoWork handler in the XAML backend)
This question already has answers here:
Wait for animation, render to complete - XAML and C#
(2 answers)
In WPF, is there a "render complete" event?
(3 answers)
How to detect when a WPF control has been redrawn?
(2 answers)
Is there a DataGrid "rendering complete" event?
(1 answer)
Showing a WPF loading message until after UI finishes updating
(2 answers)
Closed 2 years ago.
Background:
I have an application that collects data, does calculations and presents them to the user in graphs in a window. For each set of data I take a picture of the window and store it as a .png on the harddrive so that the user can go back and check the result later.
Problem:
Currently, I update the viewmodel with the new data and then have a Task.Delay(...) as to give the application some time to render the new content on the view. But sometimes I will get a picture of the previous dataset if the delay wasn't enough, I can increase the delay time to make it happen less often but that in turn will slow down the program unneccesarilly. I'm basically looking for a smart way to check if the view have been rendered with the new dataset rather than have a dumb delay.
I've looked into Window.ContentRendered event. But that only seems to fire the first time a window is rendered, so I would have to close and re-create a new window for every picture if I want to use that one and that just feels like unneccesary overhead to me. I would need something similar that fires everytime it is re-rendered, or some other way to know if the view is ready for the picture?
Short Answer: Yes, you can do this by calling your picture-saving method on the Dispatcher thread when it is idle by giving it a priority of DispatcherPriority.ApplicationIdle.
Long Answer: Here's a sample showing this at work. I have here an app that updates a viewmodel's text property when you click a button, but it takes a couple of seconds for it to update the control that is bound to it because the text is huge.
The moment I know the new data is trying to be shown, I issue a Dispatcher command to wait for the UI to be idle before I do something:
Dispatcher.Invoke((Action)(() => { // take your picture here }), DispatcherPriority.ApplicationIdle);
MainWindowViewModel.cs
public class MainWindowViewModel : INotifyPropertyChanged
{
private string messages;
private string controlText;
public MainWindowViewModel Parent { get; private set; }
public string Messages { get => this.messages; set { this.messages = value; OnPropertyChanged(); } }
public string ControlText { get => this.controlText; set { this.controlText = value; OnPropertyChanged(); } }
public void UpdateWithNewData()
{
var strBuilder = new StringBuilder();
for (int i = 0; i < 100000; i++)
{
strBuilder.AppendLine($"{DateTime.Now:HH:mm:ss.ffffff}");
}
// This will update the TextBox that is bound to this property,
// but it will take awhile because the text is HUUUUGE.
this.ControlText = strBuilder.ToString();
}
public MainWindowViewModel()
{
this.ControlText = "This area will take a while to render when you click the button below.";
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml
<Window x:Class="_65951670.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Background="LightSalmon">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox IsReadOnly="True" Text="{Binding ControlText,UpdateSourceTrigger=PropertyChanged}" TextWrapping="Wrap" Margin="5" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Visible"/>
<Button Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="15,5" Content="Update Above With Lots Of Text" Click="Button_Click"/>
</Grid>
<Grid Grid.Row="1">
<TextBox Text="{Binding Messages}" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" Margin="5" IsReadOnly="True"/>
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private MainWindowViewModel viewModel;
public MainWindow()
{
InitializeComponent();
viewModel = new MainWindowViewModel();
this.DataContext = viewModel;
this.viewModel.PropertyChanged += ViewModel_PropertyChanged;
}
private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(this.viewModel.ControlText))
{
var sw = new Stopwatch();
sw.Start();
this.viewModel.Messages += $"Property Changed: {DateTime.Now:HH:mm:ss.ffffff}\n";
// If you got here, you know that the DataContext has changed, but you don't know when it will be done rendering.
// So use Dispatcher and wait for it to be idle before performing another action.
// Put your picture-saving method inside of the 'Action' here.
Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (Action)(() =>
{
this.viewModel.Messages += $"UI Became Idle At: {DateTime.Now:HH:mm:ss.ffffff}\nIt took {sw.ElapsedMilliseconds} ms to render, Take Picture Now!";
}));
}
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this.viewModel.UpdateWithNewData();
}
}
Ok,
my problem is the following:
I have a UI showing a Canvas where i have many black circles and one red circle
So:
If i press the Button "Start" my code moves the red Circle 10 times a bit to the right. In the Logic i calculate all the intersections after every move. so i calculate them 10 times.
But now i want to update the UI after every move and show the intersections.
Here a code-example
for(int i = 0; i < 10; i++)
{
rc.xValue += 20;
calculateIntersections();
//now here the UI should be updated
Thread.Sleep(1000);
}
So i would get a "Visualization" from the calculation in the Logic.
How can i realize this?
My problem why i can´t use binding (or i don´t know the other ways) is that with a binding i would see only the last step from my moves. so i would see the redcircle after moving 200 to the right ..... But i want to see every step.
What i tried. I counted the steps and incresed this with every button-click. But thats noch comfortable. I want this like a "film" without clicking every time. And it´s much easier with many "foreach" than with many "counters".
a Property has to call PropertyChanged event that come from INotifyPropertyChanged interface to make binding work. Here is the fastest way to achieve that.
in code behind
public partial class MainWindow : Window, INotifyPropertyChanged
{
private double _rcXValue;
public double RcXValue
{
get { return _rcXValue; }
set
{
_rcXValue = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("RcXValue"));
}
}
public MainWindow()
{
InitializeComponent();
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 10; i++)
{
RcXValue += 20; //UI should be updated automatically
calculateIntersections();
await Task.Delay(1000);
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
in XAML
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="260*"/>
<RowDefinition Height="59*"/>
</Grid.RowDefinitions>
<Canvas>
<Ellipse Fill="Red" Height="17" Canvas.Left="{Binding RcXValue}" Stroke="Black" Canvas.Top="107" Width="17"/>
</Canvas>
<Button Content="Button" Grid.Row="1" Click="Button_Click"/>
</Grid>
</Grid>
</Window>
Hi all,
i need to block the screen using busyindicator. when i click the view (Sample.xaml) the event was written in (Sample.xaml.cs) that leads to execute the code that was written in a separate class named (Sample.cs). So in the separate class file i have the implementation of busyindicator. but it doesn't work.
Sample.xaml:
<UserControl x:Class="Pool.View.CadViewer"
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:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignHeight="400" d:DesignWidth="400">
<Grid>
<toolkit:BusyIndicator IsBusy="{Binding IsBusy}">
<Grid x:Name="CadLayoutRoot" MouseLeftButtonUp="CadLayoutRoot_MouseLeftButtonUp" MouseRightButtonDown="CadLayoutRoot_MouseRightButtonDown" MouseRightButtonUp="CadLayoutRoot_MouseRightButtonUp" MouseLeftButtonDown="CadLayoutRoot_MouseLeftButtonDown" MouseMove="CadLayoutRoot_MouseMove" MouseWheel="CadLayoutRoot_MouseWheel" LostMouseCapture="CadLayoutRoot_LostMouseCapture" >
</Grid>
</toolkit:BusyIndicator>
</Grid>
</UserControl>
Sample.xaml.cs:
private void CadLayoutRoot_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
UpdateViewAndViewModel();
}
private void UpdateViewAndViewModel()
{
Sample bfmobj = new BFM(View.CadViewer.radius2, View.CadViewer.model.Blocks,
View.CadViewer.Step, View.CadViewer.StepType, View.CadViewer.StepPosition);
}
Sample.cs:
public class Sample: ViewModelBase
{
public BFM(bool radius, DxfBlockCollection block, string step, string steptype, string stepposition)
{
this.Radius = radius;
this.Block = block;
this.Step = step;
this.StepType = steptype;
this.StepPosition = stepposition;
GetBFMPart();
}
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
if (_isBusy != value)
{
_isBusy = value;
OnPropertyChanged("IsBusy");
}
}
}
public void GetBFMPart()
{
IsBusy = true;
//code sample
IsBusy = false;
}
}
In the Function GetBFMPart i had the code to retrieve the data. So i need to block the Screen till the the data to be retrieved. How to do that? Please Help me to fix this issue..
Well, for a start, you're creating your Sample object but not setting it as the DataContext for your view. Either modify the existing ViewModel/DataContext or replace it with the new Sample object you've created.
private void UpdateViewAndViewModel()
{
Sample bfmobj = new BFM(View.CadViewer.radius2, View.CadViewer.model.Blocks,
View.CadViewer.Step, View.CadViewer.StepType, View.CadViewer.StepPosition);
this.DataContext = bfmobj;
}
However, I think you may also run into issues to do with threading, but I'm not very familiar with Silverlight.
In standard WPF, the framework is waiting for your function to complete before it can update the UI, as your function is running on the UI thread. And since your function effectively doesn't change the value of that bool (it does, but because nothing else can execute before it's finished, the value remains unchanged), the UI stays the same.
You need to run your code that "does the work" on a separate thread, the easiest way to do this is to use a task:
public void GetBFMPart()
{
IsBusy = true;
var task = Task.Factory.StartNew(delegate
{
// do your work here then set IsBusy = false
for (int i = 0; i < 5; i++)
{
System.Threading.Thread.Sleep(1000);
}
IsBusy = false;
});
}
I found Two Way and UpdateSourceTrigger to work
<toolkit:BusyIndicator HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
BusyContent="{Binding BusyMessage,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
IsBusy="{Binding UserControlIsBusy,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
d:IsHidden="True" />
We have a WPF application that has a ListBox with a VirtualizingStackPanel with caching. Not because it has massively many elements (typically less than 20 but perhaps up to 100 or more in extreme cases) but because elements take time to generate. The elements are in fact UIElement objects. So the application dynamically needs to generate UIElements.
The problem is that even though the virtualization appears to work, the application is still slow to become responsive, and this is in a proof of concept solution with minimal "noise".
So we figured that since the main problem is that we generate complex UIElement objects dynamically, we need to do that in parallel, i.e. off-thread. But we get an error that the code needs to be run on a STA thread:
The calling thread must be STA, because many UI components require this.
Does this mean that we cannot generate UI (UIElement objects) on thread other than the WPF main UI thread?
Here's a relevant code fragment from our proof of concept solution:
public class Person : ObservableBase
{
// ...
UIElement _UI;
public UIElement UI
{
get
{
if (_UI == null)
{
ParallelGenerateUI();
}
return _UI;
}
}
private void ParallelGenerateUI()
{
var scheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => GenerateUI())
.ContinueWith(t =>
{
_UI = t.Result;
RaisePropertyChanged("UI");
}, scheduler);
}
private UIElement GenerateUI()
{
var tb = new TextBlock();
tb.Width = 800.0;
tb.TextWrapping = TextWrapping.Wrap;
var n = rnd.Next(10, 5000);
for (int i = 0; i < n; i++)
{
tb.Inlines.Add(new Run("A line of text. "));
}
return tb;
}
// ...
}
and here is a relevant piece of XAML:
<DataTemplate x:Key="PersonDataTemplate" DataType="{x:Type local:Person}">
<Grid>
<Border Margin="4" BorderBrush="Black" BorderThickness="1" MinHeight="40" CornerRadius="3" Padding="3">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<!--<RowDefinition />-->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Name : " Grid.Row="0" FontWeight="Bold" HorizontalAlignment="Right" />
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Name}" />
<TextBlock Text=" - Age : " Grid.Column="2" Grid.Row="0" FontWeight="Bold"
HorizontalAlignment="Right" />
<TextBlock Grid.Column="3" Grid.Row="0" Text="{Binding Age}" />
<ContentControl Grid.Column="4" Grid.Row="0" Content="{Binding Path=UI}" />
</Grid>
</Border>
</Grid>
</DataTemplate>
As you can see we databind to a property UI of type UIElement.
<ListBox x:Name="listbox" ItemsSource="{Binding Persons}" Background="LightBlue"
ItemTemplate="{StaticResource PersonDataTemplate}"
ItemContainerStyle="{StaticResource ListBoxItemStyle}"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingStackPanel.ScrollUnit="Pixel"
VirtualizingStackPanel.CacheLength="10,10"
VirtualizingStackPanel.CacheLengthUnit="Item"
>
<ListBox.GroupStyle>
<GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
</ListBox.GroupStyle>
</ListBox>
In closing context, what our application does is create a code view where the list is of procedures which again contain a mix of structured content (for parameters and local variables on one hand and statements and expressions on the other.)
In other words our UIElement objects are too complex to create via databinding alone.
Another thought we had was to use "Async" settings in the XAML as it appears possible to create "non-blocking UI" but we have not been able to implement this because we get the same error as above:
The calling thread must be STA, because many UI components require this.
Stacktrace:
System.InvalidOperationException was unhandled by user code
HResult=-2146233079
Message=The calling thread must be STA, because many UI components require this.
Source=PresentationCore
StackTrace:
at System.Windows.Input.InputManager..ctor()
at System.Windows.Input.InputManager.GetCurrentInputManagerImpl()
at System.Windows.Input.KeyboardNavigation..ctor()
at System.Windows.FrameworkElement.FrameworkServices..ctor()
at System.Windows.FrameworkElement.EnsureFrameworkServices()
at System.Windows.FrameworkElement..ctor()
at System.Windows.Controls.TextBlock..ctor()
at WPF4._5_VirtualizingStackPanelNewFeatures.Person.GenerateUI() in c:\Users\Christian\Desktop\WPF4.5_VirtualizingStackPanelNewFeatures\WPF4.5_VirtualizingStackPanelNewFeatures\Person.cs:line 84
at WPF4._5_VirtualizingStackPanelNewFeatures.Person.<ParallelGenerateUI>b__2() in c:\Users\Christian\Desktop\WPF4.5_VirtualizingStackPanelNewFeatures\WPF4.5_VirtualizingStackPanelNewFeatures\Person.cs:line 68
at System.Threading.Tasks.Task`1.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
InnerException:
Edits:
1) Added more XAML.
2) Added stacktrace.
I am suffering the same problem in normal c# environment. I also tried lots of things. Do you calculate the size of controls to adjust the size of the parent in advance? I am doing this unfortunately.
You may also create a control nesting your children dynamically. By that you can create kind of an UIElement Adapter. The adapter is created at the start time and has all information to create the UIElements.
The adapter could create requested children on STA thread on demand just in time. When scrolling up or down you may create children in advance in the direction you are scrolling. This way you can start with e.g. 5-10 UI elements and then you calculate by scrolling up more.
I know this is not so nice and it would be better, if there is some technology within the framework providing something like this, but I did not found it yet.
You may look also at those two things. One helped me much in control responsive. The other is still open, since you need .NET Framework 4.5:
SuspendLayout and ResumeLayout don't operate very nice. You may try this:
/// <summary>
/// An application sends the WM_SETREDRAW message to a window to allow changes in that
/// window to be redrawn or to prevent changes in that window from being redrawn.
/// </summary>
private const int WM_SETREDRAW = 11;
/// <summary>
/// Suspends painting for the target control. Do NOT forget to call EndControlUpdate!!!
/// </summary>
/// <param name="control">visual control</param>
public static void BeginControlUpdate(Control control)
{
Message msgSuspendUpdate = Message.Create(control.Handle, WM_SETREDRAW, IntPtr.Zero,
IntPtr.Zero);
NativeWindow window = NativeWindow.FromHandle(control.Handle);
window.DefWndProc(ref msgSuspendUpdate);
}
/// <summary>
/// Resumes painting for the target control. Intended to be called following a call to BeginControlUpdate()
/// </summary>
/// <param name="control">visual control</param>
public static void EndControlUpdate(Control control)
{
// Create a C "true" boolean as an IntPtr
IntPtr wparam = new IntPtr(1);
Message msgResumeUpdate = Message.Create(control.Handle, WM_SETREDRAW, wparam,
IntPtr.Zero);
NativeWindow window = NativeWindow.FromHandle(control.Handle);
window.DefWndProc(ref msgResumeUpdate);
control.Invalidate();
control.Refresh();
}
Dispatcher.Yield
You can't change items on the UI thread from a different thread. It should work if you have a delegate on the UI thread which handles actually adding the item to the UI.
Edit:
From here:
It appears there are deeper issues with using the SynchronizationContext for UI threading.
SynchronizationContext is tied in with the COM+ support and is
designed to cross threads. In WPF you cannot have a Dispatcher that
spans multiple threads, so one SynchronizationContext cannot really
cross threads.
If it is just a one row template then consider ListView GridView.
As for dynamic content rather then dynamic UI elements use a single UI element that displays formatted content (runs, hyperlink, table).
Consider FlowDocument for Dynamic content.
FlowDocument Class
The FlowDocument can be created in background.
Also see priority binding.
PriorityBinding Class
Then you can display it with FlowDocumentScrollViewer or three other options.
I suspect adding UI elements dynamically breaks virtualization as it cannot reuse UI elements..
Have you tried:
ItemsSource="{Binding Persons, IsAsync=True}"
Or if you wand to go async in code behind, Dispatcher can help
private void ParallelGenerateUI()
{
Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)delegate()
{
_UI = GenerateUI();
RaisePropertyChanged("UI");
});
}
Just tested you code below and I get no errors:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
for (int i = 0; i < 10000; i++)
{
Persons.Add(new Person());
}
}
private ObservableCollection<Person> myVar = new ObservableCollection<Person>();
public ObservableCollection<Person> Persons
{
get { return myVar; }
set { myVar= value; }
}
}
public class Person : INotifyPropertyChanged
{
// ...
UIElement _UI;
public UIElement UI
{
get
{
if (_UI == null)
{
ParallelGenerateUI();
}
return _UI;
}
}
private void ParallelGenerateUI()
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)delegate()
{
_UI = GenerateUI();
NotifyPropertyChanged("UI");
});
}
private UIElement GenerateUI()
{
Random rnd = new Random();
var tb = new TextBlock();
tb.Width = 800.0;
tb.TextWrapping = TextWrapping.Wrap;
var n = rnd.Next(10, 5000);
for (int i = 0; i < n; i++)
{
tb.Inlines.Add(new Run("A line of text. "));
}
return tb;
}
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="info">The info.</param>
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
However I do not know what ObservableBase is doing