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();
}
}
Related
I need to open a new window while passing a string into its constructor, and this seems to preclude some important data binding in the new window.
My MainWindow consists of a TextBox where the user puts in some text and then clicks a button, opening a new window which makes an OxyPlot displaying that text. This is important because this single user input will eventually be used to open many different windows and make different plots using the same text. Can't make the user provide the string every time they open a new window.
MainWindow with some user input
The new window ("FirstPlotWindow" because there will be more windows implemented later) uses OxyPlot to plot these words at random positions. The new window also has a second TextBox and a second button for adding more words to the plot.
FirstPlotWindow with user's words plotted
I've done my best to make it so that when the user types some words into this second box and clicks the second button, more words will be added to the plot. But it doesn't work.
Here is the XAML code for FirstPlotWindow:
<Window x:Class="WpfApp2.FirstPlotWindow"
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:WpfApp2"
xmlns:oxy="http://oxyplot.org/wpf"
mc:Ignorable="d"
Title="FirstPlotWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="5*"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="userInput2" Margin="0,40,0,0" HorizontalAlignment="Center" VerticalAlignment="Top" Width="100" Height="100"/>
<Button x:Name="changePlotButton" Content="Put new words
into plot" HorizontalAlignment="Center" VerticalAlignment="Center" Click="changePlotButton_Click"/>
<oxy:PlotView x:Name="firstPlotView" Grid.Column="1" Model="{Binding MyModel}"/>
</Grid>
As you can see, I haven't set up the DataContext here because I need the plot model to take the original words ("Hello darkness my old friend") as an argument, as you can see below. Here is the code-behind for the window:
using System.Collections.Generic;
using System.Windows;
namespace WpfApp2
{
public partial class FirstPlotWindow : Window
{
private FirstPlotModel modelInWindow;
public FirstPlotModel ModelInWindow
{
get { return (FirstPlotModel)DataContext; }
set { modelInWindow = value; }
}
public FirstPlotWindow(string inputText)
{
InitializeComponent();
ModelInWindow = new FirstPlotModel(inputText);
DataContext = modelInWindow;
}
private void changePlotButton_Click(object sender, RoutedEventArgs e)
{
List<string> moreWords = new List<string>(userInput2.Text.Split(" "));
ModelInWindow.PopulatePlotWithWords(moreWords);
}
}
}
And here is the code for FirstPlotModel.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using OxyPlot;
using OxyPlot.Annotations;
using OxyPlot.Axes;
namespace WpfApp2
{
public class FirstPlotModel: INotifyPropertyChanged
{
public PlotModel MyModel { get; set; }
Random randomizer;
public event PropertyChangedEventHandler PropertyChanged;
public FirstPlotModel(string inputText)
{
MyModel = new PlotModel { Title = "First Plot" };
MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Bottom });
MyModel.Axes.Add(new LinearAxis { Position = AxisPosition.Left });
List<string> wordList = new List<string>(inputText.Split(" "));
PopulatePlotWithWords(wordList);
}
public void PopulatePlotWithWords(List<string> wordList)
{
randomizer = new Random();
foreach (string word in wordList)
{
MyModel.Annotations.Add(new TextAnnotation
{
TextPosition = new DataPoint(
randomizer.Next(1, 100),
randomizer.Next(0, 100)),
Text = word
});
}
}
private void OnPropertyChanged([CallerMemberName] String propName = null)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
}
I know that entering the second set of words and pressing the second button successfully changes the MyModel property in the ModelInWindow object, because when I put a breakpoint after changePlotButton_Click I can see that the words have been added. Since the MyModel property changed, I believe the OxyPlot should update. But the OxyPlot doesn't change.
I designed the code to be as close to this as possible: OxyPlot WPF not working with Button Click
But declaring the DataContext in XAML seems impossible here! Please help!!
I can't reproduce your code so it is hard to tell, but I can't see that you update the plot. This has to be done manually with
MyModel.InvalidatePlot(true);
OxyPlot doesn't support binding like this. Implementing INotifyPropertyChanged and OnPropertyChanged will not make an OxyPlot automatically update. Instead, you must declare a new DataContext every time you click; do this in the C# code, whether or not the binding is initially set up in XAML.
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)
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).
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>
I'd like some tips-in-the-right-direction or even ready solutions to this problem and I'm pretty stuck (I'm just beginner/intermediate):
I'm trying to implement a SSH in my application. The SSH-backend works fine and such, but I'm stuck at the frontend. What WPF-Combination would present me with an adequate solution to emulate a console? Put aside a complete terminal-emulation, I'd be happy to simply readline/writeline into something that looks like a console :-)
My best approach yet was a 80x50 Grid of single characters resulting in 4000 single cells and that feels like a total overkill.
Another idea was to make a console-Appl. bound to a wpf-window in another project. But...is that even possible and how?
Given that you want to emulate a console, I'd do it like this. Note that you'd have to handle the commands and outputting the results yourself.
page.xaml
<Window x:Class="ConsoleEmulation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" MinHeight="350" MinWidth="525" Height="350" Width="525">
<Grid>
<ScrollViewer Name="Scroller" Margin="0" Background="Black">
<StackPanel>
<ItemsControl ItemsSource="{Binding ConsoleOutput, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.}" Foreground="White" FontFamily="Consolas"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBox Text="{Binding ConsoleInput, Mode=TwoWay}" Background="Black" Foreground="White" FontFamily="Consolas" Name="InputBlock" BorderBrush="{x:Null}" SelectionBrush="{x:Null}" />
</StackPanel>
</ScrollViewer>
</Grid>
</Window>
page.xaml.cs
public partial class MainWindow : Window
{
ConsoleContent dc = new ConsoleContent();
public MainWindow()
{
InitializeComponent();
DataContext = dc;
Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
InputBlock.KeyDown += InputBlock_KeyDown;
InputBlock.Focus();
}
void InputBlock_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
dc.ConsoleInput = InputBlock.Text;
dc.RunCommand();
InputBlock.Focus();
Scroller.ScrollToBottom();
}
}
}
public class ConsoleContent : INotifyPropertyChanged
{
string consoleInput = string.Empty;
ObservableCollection<string> consoleOutput = new ObservableCollection<string>() { "Console Emulation Sample..." };
public string ConsoleInput
{
get
{
return consoleInput;
}
set
{
consoleInput = value;
OnPropertyChanged("ConsoleInput");
}
}
public ObservableCollection<string> ConsoleOutput
{
get
{
return consoleOutput;
}
set
{
consoleOutput = value;
OnPropertyChanged("ConsoleOutput");
}
}
public void RunCommand()
{
ConsoleOutput.Add(ConsoleInput);
// do your stuff here.
ConsoleInput = String.Empty;
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Did you know that you can display a Console window from your application by using AllocConsole?
This is a simple way to create a "dual-mode" application can be a
console or windows forms application.
[DllImport("kernel32")]
static extern bool AllocConsole();
Or you can use this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<TextBlock Text="Console contents..." HorizontalAlignment="Stretch" VerticalAlignment="Stretch" x:Name="ConsoleTextBlock"/>
<DockPanel Grid.Row="1">
<TextBox/>
</DockPanel>
</Grid>
For better looks, replace the TextBlock with a ListBox and style the ItemTemplate accordingly.
I haven't done it myself, however it is one of my "I'll do it if I have time"-projects.
Thus I am still looking for an existing implementation :-P
Anyways some thoughts:
The applroach to use Visuals (i.e. Ellipses, Textblocks) is probably not a good Idea.
Just think of what has to happen if you want like 200x100 characters. Maybe even a backbuffer. Holding it all in memory + drawing it....it will be incredibly slow.
Therefore the better (or even right) approach is to "draw yourself". Since WPF is backbuffered and you don't want to display an arbitrary bitmap the most likly approach would be to create a new UserControl and override it's Paint-Method.
You ma prefer to derive from Control, but UserControl may have Content, so you can show something like a connection indicator icon inside.
Architecture-wise I'd suggest to create a dependecy property Buffer (ConsoleBuffer) that holds the console buffer-model. Another DP would hold the top-left positon Location (long). It determines where to start the display (while you have a look behind). The console model I would make a class that contains a char[] and a Color[] (one dimensional). Use line breaking and \n characters to make lines (because this is the character of a console). Then if you resize the control it will re-flow without the buffer needing to be re-allocated.
You can work with **ConsoleBuffer**s of different sizes (for a different number of look behind characters).
ConsoleBuffer.Write(string s) is your method to do stuff.
Maybe it is advisable to hold arrays of arrays char[][] to represent lines.... but that is up to finding out while programming.