What is the best way to use commands in WPF ?
I use some commands, thoses commands can take a time to execute. I want that my application not freeze while running but I want the features to be disabled.
there is my MainWindow.xaml :
<Window ...>
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Button Grid.Row="0"
Grid.Column="0"
Style="{StaticResource StyleButton}"
Content="Load"
Command="{Binding LoadCommand}"/>
<Button Grid.Row="0"
Grid.Column="1"
Style="{StaticResource StyleButton}"
Content="Generate"
Command="{Binding GenerateCommand}"/>
</Grid>
</Window>
and my MainViewModel.cs :
public class MainViewModel : ViewModelBase
{
#region GenerateCommand
#endregion
#region Load command
private ICommand _loadCommand;
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(OnLoad, CanLoad);
return _loadCommand;
}
}
private void OnLoad()
{
//My code
}
private bool CanLoad()
{
return true;
}
#endregion
}
I saw a solution with background worker but I don't know how to use it. And I wonder if I should create one instance by command.
Is there a cleaner/best way ?
I want that my application not freeze while running but I want the features to be disabled.
The key to prevent the application from freezing is to perform any long-running operation on a background thread. The easiest way to do this is to start a Task. To disable the window you could bind its IsEnabled property to a source property of the view model that you set prior to starting the task. The following sample code should give you the idea:
public class MainViewModel : ViewModelBase
{
private RelayCommand _loadCommand;
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(OnLoad, CanLoad);
return _loadCommand;
}
}
private void OnLoad()
{
IsEnabled = false;
_canLoad = false;
_loadCommand.RaiseCanExecuteChanged();
Task.Factory.StartNew(()=> { System.Threading.Thread.Sleep(5000); }) //simulate som long-running operation that runs on a background thread...
.ContinueWith(task =>
{
//reset the properties back on the UI thread once the task has finished
IsEnabled = true;
_canLoad = true;
}, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
private bool _canLoad = true;
private bool CanLoad()
{
return _canLoad;
}
private bool _isEnabled;
public bool IsEnabled
{
get { return _isEnabled; }
set { _isEnabled = value; RaisePropertyChanged(); }
}
}
Note that you cannot access any UI element from a background thread since controls have thread affinity: http://volatileread.com/Thread/Index?id=1056
My approach to avoid UI freezing in these scenarios is to use async/await in the ICommand execution, and execute the long-running code on a background thread. Your modified code would look something like this:
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(async o => await OnLoadAsync(), CanLoad);
return _loadCommand;
}
}
private async Task OnLoadAsync()
{
await Task.Run(() => MyLongRunningProcess());
}
If that background task needs to update anything bound to the UI then it needs to be wrapped in a Dispatcher.Invoke (or Dispatcher.BeginInvoke).
If you want to prevent the command from being executed a second time just set "CanLoad" to true before the await Task.Run(... line, and back to false after it.
I'd suggest to use Akka.Net: you can find an example with WPF on github.
I've forked it to impement stop and start commands:
my goal was to show bidirectional communication between Akka.Net actors and ViewModel.
You'll find the ViewModel calling the ActorSystem like this
private void StartCpuMethod() {
Debug.WriteLine("StartCpuMethod");
ActorSystemReference.Start();
}
private void StopCpuMethod() {
Debug.WriteLine("StopCpuMethod");
ActorSystemReference.Stop();
}
with an Actor receiving those messages
public CPUReadActor()
{
Receive<ReadCPURequestMessage>(msg => ReceiveReadDataMessage());
Receive<ReadCPUSyncMessage>(msg => ReceiveSyncMessage(msg));
}
private void ReceiveSyncMessage(ReadCPUSyncMessage msg)
{
switch (msg.Op)
{
case SyncOp.Start:
OnCommandStart();
break;
case SyncOp.Stop:
OnCommandStop();
break;
default:
throw new Exception("unknown Op " + msg.Op.ToString());
}
}
and the other way round from an Actor
public ChartingActor(Action<float, DateTime> dataPointSetter)
{
this._dataPointSetter = dataPointSetter;
Receive<DrawPointMessage>(msg => ReceiveDrawPointMessage(msg));
}
private void ReceiveDrawPointMessage(DrawPointMessage msg)
{
_dataPointSetter(msg.Value, msg.Date);
}
to the ViewModel
public MainWindowViewModel()
{
StartCpuCommand = new RelayCommand(StartCpuMethod);
StopCpuCommand = new RelayCommand(StopCpuMethod);
SetupChartModel();
Action<float, DateTime> dataPointSetter = new Action<float, DateTime>((v, d) => SetDataPoint(v, d));
ActorSystemReference.CreateActorSystem(dataPointSetter);
}
private void SetDataPoint(float value, DateTime date)
{
CurrentValue = value;
UpdateLineSeries(value, date);
}
The best way here it's a use of async/await, in my opinion. https://msdn.microsoft.com/ru-ru/library/mt674882.aspx
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
LoadCommand = new RelayCommand(async ol => await OnLoadAsync(), CanLoad);
}
public ICommand LoadCommand { get; }
private async void OnLoadAync()
{
await SomethingAwaitable();
}
private Task<bool> SomethingAwaitable()
{
//Your code
}
}
Related
It's most likely a really stupid mistake but I cannot figure this out and I've spent almost 2 days on this.
I have an app that with a button click launches 5 tasks running in parallel but each with its own delay. This part works fine.
However, if you click the same button again, it doesn't cancel previously launched tasks and creates another instance. So basically creates more and more tasks that run in parallel. I obviously have code trying to cancel tasks if the button is clicked more than once but for some reason it doesn't work.
Could someone point me to my issue? Or is this code is beyond repair and needs total revamp? Thank you!
If you execute this WPF app and click on the button, you will see that ping frequency just keeps on increasing.
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace TestMultiThreadWithDiffSleeps
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
#region Binding
private string m_output0;
public string Output0
{
get { return m_output0; }
set { m_output0 = value; OnPropertyChanged(); }
}
private string m_output1;
public string Output1
{
get { return m_output1; }
set { m_output1 = value; OnPropertyChanged(); }
}
private string m_output2;
public string Output2
{
get { return m_output2; }
set { m_output2 = value; OnPropertyChanged(); }
}
private string m_output3;
public string Output3
{
get { return m_output3; }
set { m_output3 = value; OnPropertyChanged(); }
}
private string m_output4;
public string Output4
{
get { return m_output4; }
set { m_output4 = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
private static SemaphoreSlim ThreadSemaphore;
private CancellationTokenSource CancellationTokenSrc;
public MainWindow()
{
InitializeComponent();
DataContext = this;
ThreadSemaphore = new SemaphoreSlim(1, 1);
}
private async void ButtonStart_Click(object sender, RoutedEventArgs e)
{
await StartToMonitor();
}
private async Task<bool> StartToMonitor()
{
M_Stop(); // Stop everything in case this is a restart
CancellationTokenSrc = new CancellationTokenSource();
bool taskResult = await M_Start();
CancellationTokenSrc = null;
return taskResult;
}
public void M_Stop()
{
Output0 = string.Empty;
Output1 = string.Empty;
Output2 = string.Empty;
Output3 = string.Empty;
Output4 = string.Empty;
if (CancellationTokenSrc != null)
CancellationTokenSrc.Cancel();
}
private async Task<bool> M_Start()
{
List<Task<bool>> tasks = new List<Task<bool>>();
// Build task list
for (int i = 0; i < 5; i++)
{
int iIdx = i; // Need to do this when multi-threading
int sleepTime = (i + 1) * 1000;
tasks.Add(Task.Run(async () =>
{
while (!CancellationTokenSrc.Token.IsCancellationRequested)
{
if (!Ping(iIdx))
return false; // Ping in this example always returns 'true' but you can imagine in real life there'd be 'false' returns that shall cancel all threads
await Task.Delay(sleepTime); // Delay for different length of time for each thread
}
return true;
}, CancellationTokenSrc.Token));
}
Task<bool> firstFinishedTask = await Task.WhenAny(tasks);
bool result = firstFinishedTask.Result;
CancellationTokenSrc.Cancel(); // Cancel all other threads as soon as one returns
return result;
}
private bool Ping(int index)
{
ThreadSemaphore.Wait(); // Not needed for this app... here only because it's in my other app I'm troubleshooting
switch (index)
{
case 0: Output0 += "*"; break;
case 1: Output1 += "*"; break;
case 2: Output2 += "*"; break;
case 3: Output3 += "*"; break;
case 4: Output4 += "*"; break;
}
ThreadSemaphore.Release();
return true;
}
}
}
<Window x:Class="TestMultiThreadWithDiffSleeps.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"
Title="MainWindow" SizeToContent="WidthAndHeight">
<StackPanel>
<Button Content="Start" HorizontalAlignment="Left" VerticalAlignment="Top" Width="200" Margin="0,30,0,0" Click="ButtonStart_Click"/>
<TextBox Text="{Binding Output0}"/>
<TextBox Text="{Binding Output1}"/>
<TextBox Text="{Binding Output2}"/>
<TextBox Text="{Binding Output3}"/>
<TextBox Text="{Binding Output4}"/>
</StackPanel>
</Window>
There are a couple of issues in this code.
The source of the problem you're describing is this:
tasks.Add(Task.Run(async () =>
{
while (!CancellationTokenSrc.Token.IsCancellationRequested)
The tasks always check the token of the current CancellationTokenSrc. So unless the cancellation happens in the exact moment between Task.Delay calls where the tasks check IsCancellationRequested, they'll just see the new, uncancelled CTS you create after a restart.
You should pass the current CancellationToken as a method argument or store it in a local variable inside M_Start, instead of checking the shared CancellationTokenSrc field to avoid this problem.
Slight improvement: Task.Delay also has an overload that accepts a CancellationToken. It will throw a TaskCanceledException.
Additionally, you're updating the OutputX properties from parallel threads (Task.Run), which then raise the PropertyChanged event that's supposed to update the UI. This kind of cross-thread UI interaction isn't safe, you will need to involve the Dispatcher to make sure the event is raised on the UI thread.
Lastly, the ownership of CancellationTokenSrc is fairly complex and seems ripe for race conditions as multiple concurrent methods use and set it. In case of a restart M_Start could easily cause a NullReferenceException when it tries to cancel CancellationTokenSrc which has already been set to null in StartToMonitor.
I think you need to make Task.Delay cancellable too:
await Task.Delay(sleepTime);
Change it to:
await Task.Delay(sleepTime, CancellationTokenSrc.Token);
I have an error "Must create DependencySource on same Thread as the DependencyObject" in my project.
My comment is used to load a file and create a list. This list is bind to a ListBox. AL was working good. But I created a Task to load (load can be long). Now I have this error. I don't understand why it occurs.
There is my code :
MainView.xaml:
<ListBox ItemsSource="{Binding Results}"
SelectedItem="{Binding SelectedItem}">
<ListBox.InputBindings>
<KeyBinding Command="{Binding RemoveCommand}"
Key="Delete"/>
</ListBox.InputBindings>
</ListBox>
<Button Grid.Row="1" Grid.Column="0"
Style="{StaticResource StyleButton}"
Command="{Binding LoadCommand}"
Content="Open result"/>
MainViewModel:
#region Fields/Properties
public ImageWithPoints SelectedItem
{
get
{
return _selectedItem;
}
set
{
_selectedItem = value;
SelectedPointIndex = 1;
OnPropertyChanged();
OnPropertyChanged("Picture");
UpdatePoints();
}
}
public List<ImageWithPoints> Results
{
get
{
return _results;
}
set
{
_results = value;
if (value == null)
{
SelectedPointIndex = 0;
}
OnPropertyChanged();
}
}
public BitmapSource Picture
{
get
{
return SelectedItem?.Picture;
}
}
#endregion
#region Load
private ICommand _loadCommand;
public ICommand LoadCommand
{
get
{
if (_loadCommand == null)
_loadCommand = new RelayCommand(OnLoad, CanLoad);
return _loadCommand;
}
}
public void OnLoad()
{
StartRunning(this, null);
Task loadTask = new Task(new Action(() =>
{
Load();
Application.Current.Dispatcher.Invoke(new Action(() =>
{
StopRunning(this, null);
}));
}));
loadTask.Start();
}
public bool CanLoad()
{
return !IsRunning;
}
#endregion
#region Events
public event EventHandler OnStartRunning;
public event EventHandler OnStopRunning;
private void StartRunning(object sender, EventArgs e)
{
OnStartRunning(sender, e);
}
private void StopRunning(object sender, EventArgs e)
{
OnStopRunning(sender, e);
}
#enregion
#region Methods
public void Load()
{
// Open File
// Set to list
List<ImageWithPoints> listRes;
Results = listRes;
SelectedItem = Results[0];
}
#endregion
When I remove the line SelectedItem = Results[0]; I have no error (but application don't work has it should).
Set the SelectedItem property back on the UI thread once the Task has finished:
public void OnLoad()
{
StartRunning(this, null);
Task.Factory.StartNew(new Action(() =>
{
Load();
})).ContinueWith(task =>
{
SelectedItem = Results[0];
StopRunning(this, null);
}, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
You can only access a UI element on the thread on which it was originally created so if your UpdatePoints() method accesses any control you must call this method on the UI thread.
I'm relatively new to MVVM and I'm wondering about the best way to structure my application. Here is a sample of my models:
public class ModelSource : ModelBase
{
#region Fields
private int _isLoading;
private BackgroundWorker worker = new BackgroundWorker();
private ObservableCollection<PCDatabase> _databases;
#endregion //Fields
#region Properties
public ObservableCollection<PCDatabase>Databases
{
get
{
if (_databases == null)
{
_databases = new ObservableCollection<PCDatabase>();
}
return _databases;
}
set
{
_databases = value;
this.OnPropertyChanged("Databases");
}
}
public int IsLoading
{
get
{
return _isLoading;
}
set
{
_isLoading = value;
OnPropertyChanged("IsLoading");
}
}
#endregion
#region Methods
/// <summary>
/// Gets all Databases from the Server
/// </summary>
public void getDatabasesAsync(ConfigDatabaseConnection _currentConfig)
{
//execute SQL Query...
}
(ModelBase implements INotifyPropertyChanged).
Here is my corresponding ViewModel:
namespace DbRestore.ViewModel
{
public class ViewModelSource : ViewModelBase
{
private ObservableCollection<PCDatabase> _databases;
private ModelSource _modelSource;
private ICommand _populateDatabaseCommand;
public ConfigDatabaseConnection _currentConfig;
public ViewModelSource()
{
this.ModelSource = new ModelSource();
}
#region Commands
/// <summary>
/// Command that opens a Database Connection Dialog
/// </summary>
public ICommand OpenDataBaseConnectionCommand
{
get
{
if (_populateDatabaseCommand == null)
{
_populateDatabaseCommand = new RelayCommand(
param => this.PopulateDatabases()
);
}
return _populateDatabaseCommand;
}
}
public ObservableCollection<PCDatabase> Databases
{
get
{
return _databases;
}
set
{
_databases = value;
OnPropertyChanged("Databases");
}
}
#endregion //Commands
public void PopulateDatabases()
{
ModelSource.getDatabasesAsync(_currentConfig);
}
Calling ModelSource.getDatabasesAsync(_currentConfig) gets my SQL Data in my model. Due to some of my SQL queries being quite complex, I've implemented a Background Worker that runs these queries asynchronously.
How do I get the data into my ViewModel, which is bound to my View? Or is my design approach as a whole faulty?
Things I've considered and tried:
Binding directly to the model: Works, but I've been told that this is a
bad practice, and the application logic should reside in the Model.
Moving the SQL queries into the ViewModel: Also works, but then my Model
class seems to be redundant - it would be nothing but a custom datatype.
Run the queries synchronously and directly assign the Observable
Collection in my model to the Observable Collection in my ViewModel. Also
works, but then I'm running into problems with my BackgroundWorker,
because the ViewModel won't know when the Query is actually finished.
Move all your database logic into a service (aka repository) class.
It is OK to bind directly to the model properties instead of creating a dozen ViewModel proxy classes for each Model, as soon as you don't need any special view-related logic around a particular model. So exposing a collection of PCDatabase is OK.
Since you're using BackgroundWorker, I assume you use .NET Framework 3.5 and don't have TPL.
public interface IPCDatabaseRepository
{
void GetPCDatabasesAsync(Action<IList<PCDatabase>> resultHandler);
}
public class PCDatabaseRepository : IPCDatabaseRepository
{
public void GetPCDatabasesAsync(Action<IList<PCDatabase>> resultHandler)
{
var worker = new BackgroundWorker();
worker.DoWork += (sender, args) =>
{
args.Result = // Execute SQL query...
};
worker.RunWorkerCompleted += (sender, args) =>
{
resultHandler(args.Result as IList<PCDatabase>);
worker.Dispose();
};
worker.RunWorkerAsync();
}
}
public class ViewModelSource : ViewModelBase
{
private readonly IPCDatabaseRepository _databaseRepository;
private ObservableCollection<PCDatabase> _databases;
private bool _isBusy;
public ViewModelSource(IPCDatabaseRepository databaseRepository /*Dependency injection goes here*/)
{
_databaseRepository = databaseRepository;
LoadDatabasesCommand = new RelayCommand(LoadDatabases, () => !IsBusy);
}
public ICommand LoadDatabasesCommand { get; private set; }
public ObservableCollection<PCDatabase> Databases
{
get { return _databases; }
set { _databases = value; OnPropertyChanged("Databases"); }
}
public bool IsBusy
{
get { return _isBusy; }
set { _isBusy = value; OnPropertyChanged("IsBusy"); CommandManager.InvalidateRequerySuggested(); }
}
public void LoadDatabases()
{
IsBusy = true;
_databaseRepository.GetPCDatabasesAsync(results =>
{
Databases = new ObservableCollection(results);
IsBusy = false;
});
}
Have you seen these articles?
Async Programming : Patterns for Asynchronous MVVM Applications: Data Binding
https://msdn.microsoft.com/en-us/magazine/dn605875.aspx
Async Programming : Patterns for Asynchronous MVVM Applications: Commands
https://msdn.microsoft.com/en-us/magazine/dn630647.aspx
These should cover a good strategy especially when working with async/await.
Scenario
Some date are loaded into a program (e.g., evaluation of students in a class where each student is a distinct entity with his/her evaluation data) and a summary of them is shown on a datagrid. The user selects selects some of the students, and performs an analysis on their evaluation. The analysis process requires some parameters, therefore before analysis a window pops-up and lets user to specify his preferred parameters; then the analysis process executes.
Implementation summary
The datagrid is defined as following and binded to a ViewModel:
<DataGrid x:Name="CachedSamplesDG" ItemsSource="{Binding cachedDataSummary}">
<DataGrid.Columns>
<DataGridTextColumn Header="name" Binding="{Binding name}"/>
<DataGridTextColumn Header="score" Binding="{Binding score}"/>
</DataGrid.Columns>
</DataGrid>
The button that starts the process is defined as following:
<Button x:Name="AnalysisBT" Content="Analyze" Command="{Binding AnalyzeCommand}" CommandParameter="{Binding ElementName=CachedSamplesDG, Path=SelectedItems}"/>
The ViewModel is pretty basic and summarized as following:
internal class CachedDataSummaryViewModel
{
public CachedDataSummaryViewModel()
{
_cachedDataSummary = new ObservableCollection<CachedDataSummary>();
AnalyzeCommand = new SamplesAnalyzeCommand(this);
}
private ObservableCollection<CachedDataSummary> _cachedDataSummary;
public ObservableCollection<CachedDataSummary> cachedDataSummary { get { return _cachedDataSummary; } }
public ICommand AnalyzeCommand { get; private set; }
}
And here is the definition of analysis command:
internal class SamplesAnalyzeCommand : ICommand
{
public SamplesAnalyzeCommand(CachedDataSummaryViewModel viewModel)
{
_viewModel = viewModel;
}
private CachedDataSummaryViewModel _viewModel;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
// canExecute logic
}
public void Execute(object parameter)
{
// process mess ...
// Here I need the selected rows of datagird, which "parameter" delegates them.
// I also need some other parameters for analysis which user can set through another view
}
}
An this is a diagram of my current process and what I would like to do next
Question
When the button is clicked
Apply some UI changes on MainWindow
Pop-up ProcessOptionsWindow
Get set parameters from ProcessOptionsWindow
Pass the selected datagrid rows and user specified parameters to SamplesAnalyzeCommand
What would be the best way to achieve this requirement ?
simply use a dialogservice like Good or bad practice for Dialogs in wpf with MVVM?.
then you can do something like this in your ViewModel
var result = this.uiDialogService.ShowDialog("Prozess Options Window", prozessOptionVM);
...
var parameter1 = prozessOptionVM.Parameter1;
You can define another Model and ViewModel for Process Options, and then in the SamplesAnalyzeCommand, display the ProcessOptionsView. When user is done with the ProcessOptionsView, the main ViewModel gets notified (e.g by an event handler) and completes the Process.
Something like this:
internal class SamplesAnalyzeCommand : ICommand {
...
public void Execute(object parameter)
{
this._viewModel.ShowProcessOptions(parameter);
}
}
internal class CachedDataSummaryViewModel {
public string Status {
get {
return this.status;
}
set {
if (!string.Equals(this.status, value)) {
this.status = value;
// Notify property change to UI
}
}
}
...
internal void ShowProcessOptions(object paramter) {
// Model
var processOptions = new ProcessOptionsModel() {
otherInfo = parameter
};
// View-Model
var processOptionsViewModel = new ProcessOptionsViewModel();
processOptionsViewModel.Model = processOptions;
// View
var processOptionsView = new ProcessOptionsView(
processOptionsViewModel
);
// Edit2: Update status
this.Status = "Selecting process options...";
// You can use the event handler or dialog result
processOptionsViewModel.OK += this.PerformProcess;
processOptionsView.ShowDialog();
}
private void PerformProcess(object sender, EventArgs e) {
var processOptionsView = sender as ProcessOptionsView;
var processOptionsModel = processOptionsView.Model;
var processOptions = processOptionsModel.Model;
// Edit2: Update status
this.Status = "Performing process...";
// use processOptions.OtherInfo for initial info
// use processOptions.* for process options info
// and perform the process here
// Edit2: Update status
this.Status = "Process Done.";
}
...
}
class ProcessOptionsModel {
public object OtherInfo {
get;
set;
public int Parameter1 {
get;
set;
}
public IList<ProcessItem> SelectedItems {
get;
set;
}
...
}
class ProcessOptionsViewModel {
public event EventHandler OK;
private SamplesAnalyzeCommand model;
private ICommand okCommand;
public ProcessOptionsViewModel() {
this.okCommand = new OKCommand(this.OnOK);
}
public SamplesAnalyzeCommand Model {
get {
return model;
}
set {
this.model = value;
// Property changed stuff here
}
}
private void OnOK(object parameter) {
if (this.OK != null) {
this.OK = value;
}
}
}
class ProcessOptionsView {
// Interacts with it's view-model and performs OK command if
// user pressed OK or something
}
Hope it helps.
Edit (1):
As blindmeis suggested, you may use some Dialog Service to make the connection between the views.
Edit (2):
Immidiate UI changes after button click can be done in ShowProcessOptions method of the ShowProcessOptions. I don't think you want reflect ui changes of the options window while user works with it, to the main window. UI changes after user closes options window can be done in PerformProcess.
If you want to make an abstraction for options selection (e.g reading from a file) as you mentioned in the comment below, you may define an IOptionsProvider interface, and put ProcessOptionsView and View-Model behind that but still you use the same model.
interface IOptionsProvider {
ProcessOptionsModel GetProcessOptions();
}
class ProcessOptionsView : IOptionsProvider {
public ProcessOptionsModel GetProcessOptions() {
if (this.ShowDialog()) {
return this.ModelView.Model;
}
return null;
}
}
class ProcessOptionsFromFile : IOptionsProvider {
public ProcessOptionsModel GetProcessOptions() {
// Create an instance of ProcessOptionsModel from File
}
}
Note that in this case I removed the OK event since the GetProcessOptions is supposed to block until user closes the main window. If you want a responsive approach in the FromFile case, you may need to work on the async stuff, maybe define GetProcessOptionsAsync instead.
In this case things may get a little bit complicated but I guess it is achievable in this way.
Hi I'm a novice in writing C# applications.
Sorry if its too basic.
I have a thread running in Main xaml which queries for some information and updates a property.
So once I detect that property is set to "X" I need to switch to a different XAML view.
The problem I'm facing is when I invoke the switch from the Property, my app crashes.
I think it has to do with the thread..
Qn : How do I switch to a different XAML view as soon as I detect the property value changed?
Sample Code :
public partial class MainWindow : Window
{
....
private Thread t;
public static enState dummy;
public enState SetSTATE
{
get
{
return dummy;
}
set
{
dummy = value;
if (dummy == A )
{
var NEWVIEW = new VIEW1();
contentGrid.Children.Add(NEWVIEW); // - crashes in this block
}
}
}
public void startThread()
{
t = new Thread(getInfo);
t.Isbackground = true;
t.start();
}
public void getInfo()
{
while (true)
{
int x = somefunc();
if (x == conditon)
{
SetSTATE = A;
}
Thread.Sleep(1000);
}
}
MainWindow() { startThread(); }
}
public partial class NEWVIEW: UserControl
You can't modify a collection from a background thread. You'll need to explicitly use Dispatcher.BeginInvoke to make the modification:
if (dummy == A )
{
contentGrid.Dispatcher.BeginInvoke(new Action(() =>
{
var NEWVIEW = new VIEW1();
contentGrid.Children.Add(NEWVIEW);
}));
}