I'm just trying to get a grasp of MVVM pattern in WPF (currently without any framework).
Scenario:
I have a main window, I click a button "Start work" that is bound to some command in the viewmodel. Progress dialog should open with "Cancel" button, it should show on the center of the owner window(so I need to pass the owner), I press cancel and I invoke "CancelAsync" method on background worker.
The principle of MVVM is that the view model should never know anything about the view and in my case I'm violating this rule.
Code-behind (No MVVM) solution:
Main window part:
private void Button_Click(object sender, RoutedEventArgs e)
{
backgroundWorker.RunWorkerAsync();
progressWindow = new ProgressWindow(backgroundWorker);
progressWindow.Owner = this;
progressWindow.ShowDialog();
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressWindow.Close();
}
Progress window part:
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
backgroundWorker.CancelAsync();
}
My attempt to convert this code to MVVM (this is wrong)
public class MainViewModel
{
public ICommand DoSomething { get; }
private BackgroundWorker backgroundWorker;
private PleaseWaitView pleaseWaitView;
public MainViewModel()
{
backgroundWorker = new BackgroundWorker() { WorkerSupportsCancellation = true };
backgroundWorker.DoWork += BackgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
var pleaseWaitViewModel = new PleaseWaitViewModel(backgroundWorker);
pleaseWaitView = new PleaseWaitView();
pleaseWaitView.Owner = Application.Current.MainWindow;
pleaseWaitView.DataContext = pleaseWaitViewModel;
DoSomething = new ActionCommand<object>(DoSomethingImpl);
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
pleaseWaitView.Close();
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Some work
Thread.Sleep(5000);
}
private void DoSomethingImpl(object parameter)
{
pleaseWaitView.ShowDialog();
}
}
How to solve this? I did what I wanted in code-behind in a matter of 20 minutes, I wanted to try MVVM pattern and it takes me few hours to solve simple problem.
I was looking at some solutions with EventAggregator but that requires using a framework like Prism, Caliburn.Micro. So I get some kind of communication between VM and the View.
You can pass an interface to MainViewModel which contains the needed methods
interface IMainView
{
void Init(PleaseWaitViewModel viewModel);
void ShowDialog();
void Close();
}
public class MainViewModel
{
private IMainView _view;
public MainViewModel(IMainView view)
{
_view = view;
backgroundWorker = new BackgroundWorker() { WorkerSupportsCancellation = true };
backgroundWorker.DoWork += BackgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted +=
BackgroundWorker_RunWorkerCompleted;
var pleaseWaitViewModel = new PleaseWaitViewModel(backgroundWorker);
_view.Init(pleaseWaitViewModel);
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
_view.Close();
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Some work
Thread.Sleep(5000);
}
private void DoSomethingImpl(object parameter)
{
_view.ShowDialog();
}
}
Messenger approach
public class PersonsViewModel
{
private RelayCommand _addPersonCommand = null;
public RelayCommand AddPersonCommand
{
get
{
return _addPersonCommand ?? (_addPersonCommand = new RelayCommand(
() =>
{
Action<Person> callback = (person) =>
{
_persons.Add(person);
RaisePropertyChanged("Persons");
};
Messenger.Default.Send<NotificationMessageAction<Person>>(new NotificationMessageAction<Person>(this, new Person(), "myNotification", callback), this);
}));
}
}
}
private PersonsViewModel _viewModel = null;
public PersonsView()
{
InitializeComponent();
DataContext = _viewModel = new PersonsViewModel();
Messenger.Default.Register<NotificationMessageAction<Person>>(this, _viewModel, message =>
{
if(message.Notification == "myNotification")
{
Person person = (Person)message.Target;
Action<Person> callback = message.Execute;
ModalView view = new ModalView(person);
if(true == view.ShowDialog())
{
callback.Invoke(view.Person);
}
}
});
}
Action property on view model approach
1) Add action property on the viewmodel
2) Wire it up in the view code behind
3) Invoke action it in the viewmodel logic where needed
using System;
using System.Windows;
using System.Windows.Input;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Wire up CancelAction in the View
var windowToClose = new Window();
var castedContext = (ViewModel) DataContext;
castedContext.CancelAction = () => windowToClose.Close();
}
}
public class ViewModel
{
private ICommand _doSomethingCommand;
public Action CancelAction { get; set; }
public ICommand DoSomethingCommand
{
get
{
if (_doSomethingCommand != null)
return _doSomethingCommand;
_doSomethingCommand = new MyCommandImplementation(() =>
{
// Perform Logic
// If need to cancel - invoke cancel action
CancelAction.Invoke();
});
return _doSomethingCommand;
}
}
}
// Stubbed out for the sake of complete code
public class MyCommandImplementation : ICommand
{
public MyCommandImplementation(Action action)
{
throw new NotImplementedException();
}
public bool CanExecute(object parameter)
{
throw new NotImplementedException();
}
public void Execute(object parameter)
{
throw new NotImplementedException();
}
public event EventHandler CanExecuteChanged;
}
}
Related
not very expert of Threading under Windows.
I have a Main WinForm that opens a child form in it's ctor.
public partial class Main : Form
{
public Main()
{
InitializeComponent();
ImgRxUI formStart = new ImgRxUI();
formStart.MdiParent = this;
formStart.WindowState = FormWindowState.Maximized;
formStart.Show();
}
etc..
The ImgRxUI Form (child form) starts a Thread passing to 2 Actions (delegates in simple form).
public partial class ImgRxUI : Form
{
private ImgReceiver oImgReceiver = null;
public ImgRxUI()
{
InitializeComponent();
this.WindowState = FormWindowState.Maximized;
this.ShowIcon = false;
oImgReceiver = new ImgReceiver(UpdateImage, Log);
oImgReceiver.startService();
}
public void UpdateImage(byte[] ProtocolType)
{
...do stuff...
}
public void Log(string Text)
{
this.Invoke((MethodInvoker)delegate
{
LogMethod(Text);
tLog.ScrollToCaret();
});
}
private void LogMethod(string Text)
{
tLog.AppendText(Text + Environment.NewLine);
}
The ImgReceiver as I said starts a thread that listens on a socket...
public class ImgReceiver
{
private Action<byte[]> ImgReceived;
private Action<string> Log;
private System.Threading.Thread Thread_ImgReceiver = null;
public ImgReceiver(Action<byte[]> ImageReceivedDelegate, Action<string> LogDelegate)
{
this.ImgReceived = ImageReceivedDelegate;
this.Log = LogDelegate;
}
public void startService()
{
Thread_ImgReceiver = new System.Threading.Thread(startListening);
Thread_ImgReceiver.IsBackground = true;
Thread_ImgReceiver.Start();
}
[SecurityPermissionAttribute(SecurityAction.Demand, ControlThread = true)]
public void killService()
{
Thread_ImgReceiver.Abort();
System.Threading.Thread.Sleep(1000);
}
public void startListening()
{ ...do stuff...}
When I close the ImgRxUI form the following event on the form itself gets called
private void ImgRxUI_FormClosing(object sender, FormClosingEventArgs e)
{
oImgReceiver.killService();
}
Hear rises the error in the title.
Wht ?
Thaks
Change the kill Service method to
[SecurityPermissionAttribute(SecurityAction.Demand, ControlThread = true)]
public void killService(Action action)
{
action.Invoke();
System.Threading.Thread.Sleep(1000);
}
Change the access level Thread_ImgReceiver to public
public System.Threading.Thread Thread_ImgReceiver = null;
and call killService to
private void ImgRxUI_FormClosing(object sender, FormClosingEventArgs e)
{
oImgReceiver.killService(new Action(delegate { oImgReceiver.Thread_ImgReceiver.Abort(); }));
}
I'm currently stuck on how to update the text of my textbox whenever any class changes the text. Currently, only my main thread does so, and I have tried various methods (including a dispatch) to update the view from anywhere. My code looks like this:
XAML:
<TextBox x:Name ="textBoxResults" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}"/>
XAML.CS:
public partial class MainWindow : Window
{
public ConsoleLog cl { get; set; }
public MainWindow()
{
InitializeComponent();
MainWindow_Load();
cl = new ConsoleLog();
DataContext = cl;
}
}
private void ButtonBeginTests_Click(object sender, RoutedEventArgs e)
{
new Thread(() =>
{
App.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new Action(() =>{
tc = new TestController(.., .., cl); //other args not important
tc.beginTest();
}));
}).Start();
}
ConsoleLog Class:
using System;
using System.ComponentModel;
namespace Test_DesktopApplication.src
{
public class ConsoleLog : INotifyPropertyChanged
{
private string text;
public string Text
{
get { return text; }
set
{
text = value;
OnPropertyChanged("Text");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public void addToLog(string text)
{
App.Current.Dispatcher.Invoke(new Action(delegate { this.Text += text; }));
}
}
A class calls "addToLog" multiple times during a separate thread of background processes. Any indication to what I could be doing wrong?
EDIT: I can actually get this to work by using this after every addToLog call:
App.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new ThreadStart(delegate { }));
however I don't know if this is a "good" fix.
EDIT 2:
I have updated the MainWindow class to show when this class is called, the class that calls the log is below:
public class Testcontroller
{
ConsoleLog cl;
public GEN2TestController(.., .., ConsoleLog console)
{
//other constructor things
cl = console;
}
public void beginTest(){
testList[0].result = unprogrammedCurrent_test(.., ..); //this is an example of what the test looks like..
cl.addToLog(TestList[0].result);
...//repeat for the rest of the test lists and tests..
...
...
}
The log will not update until all tests are done.
You code example is working fine!
EDIT: There should be no need to force an empty dispatcher cycle like that, this is the equivalent of the old WinForms Application.DoEvents() there must be something else in your code that is blocking the dispatcher (UI thread), perhaps you should share an example of how your background worker is constructed and initiated.
I added a button to your form:
<Button Margin="0,267,376,0" Click="Button_Click" Height="54" VerticalAlignment="Top" HorizontalAlignment="Right" Width="142"/>
And the button click logic in the code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
cl.addToLog(DateTime.Now.ToString() + Environment.NewLine);
}
I suspect that you issue then is how you are calling addToLog() Most likely you are calling a different instance of the object to the one that is set as the data context.
I have modified your example to include a Background worker that is initiated from the form, this is working quite well:
public partial class MainWindow : Window
{
private void Button_Click(object sender, RoutedEventArgs e)
{
if (worker.IsBusy)
worker.CancelAsync();
else
{
cl.Text = String.Empty;
worker.RunWorkerAsync();
}
}
public ConsoleLog cl { get; set; }
private BackgroundWorker worker = null;
public MainWindow()
{
InitializeComponent();
cl = new ConsoleLog();
DataContext = cl;
worker = new BackgroundWorker { WorkerSupportsCancellation = true };
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e) => cl.addToLog($"{e.Result}");
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
Random r = new Random();
while(true)
{
if ((sender as BackgroundWorker).CancellationPending) break;
cl.addToLog(DateTime.Now.ToString() + Environment.NewLine);
System.Threading.Thread.Sleep(r.Next(500, 3000));
}
e.Result = "Stop" + Environment.NewLine;
}
}
I have a winform that subscribes to events from a callback interface. I use...
public partial class Form1 : Form
{
MyTestCallBack _callback;
public Form1()
{
InitializeComponent();
//Start otherthread
API.Create()
_callback = new MyTestCallBack();
_callback.MyTestCallBackEvent += callback_MyTestCallBackEvent;
_callback.OnUpdate();
}
private callback_MyTestCallBackEvent(MyTestCallBackEventArgs e)
{
if (InvokeRequired)
{
Invoke(new Action(() =>
{
callback_MyTestCallBackEvent(sender, e);
}));
return;
}
label1.Text = e.SomeObject.GetDisplayString();
} }
class MyTestCallBackEventArgs : EventArgs {
public SomeObject SomeObj { get; set; } }
class MyTestCallBack : Callback {
public event EventHandler<MyTestCallBackEventArgs> MyTestCallBackEvent;
protected virtual void OnMyTestCallBackEvent(MyTestCallBackEventArgs e)
{
if (MyTestCallBackEvent != null)
MyTestCallBackEvent(this, e);
}
public void OnUpdate(SomeObject someobj)
{
OnMyTestCallBackEvent(new MyTestCallBackEventArgs { SomeObject = someobj });
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
//tried this but still crashes
_callback.MyTestCallBackEvent -= callback_MyTestCallBackEvent;
API.Destroy()
}
When I try to shutdown my form it crashes. "Freezes". I assume it is because of the other thread not shuting down correctly.
How do safely unsubscribe from this event so that I can close down correctly.
To immediately unsubscribe from the event, use:
_callback.MyTestCallBackEvent -= callback_MyTestCallBackEvent;
I've been trying to refactor a spaghetti code of an app by using MVP pattern. But now I'm struggling with this:
A form that has button that calls a the DoWork method (of a backgroundworker) which is a long operation. My question is if I move the long operation out of the view into the Presenter then how do I send progress changes from this operation to the View? The BGW must be in the Presenter also?
Can you give me a sample of how to do this?
Thank you in advance.
This outlines the use of the BackgroundWorker:
private BackgroundWorker _backgroundWorker;
public void Setup( )
{
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.DoWork +=
new DoWorkEventHandler(BackgroundWorker_DoWork);
_backgroundWorker.ProgressChanged +=
new ProgressChangedEventHandler(BackgroundWorker_ProgressChanged);
_backgroundWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(BackgroundWorker_RunWorkerCompleted);
// Start the BackgroundWorker
_backgroundWorker.RunWorkerAsync();
}
void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// This method runs in a background thread. Do not access the UI here!
while (work not done) {
// Do your background work here!
// Send messages to the UI:
_backgroundWorker.ReportProgress(percentage_done, user_state);
// You don't need to calculate the percentage number if you don't
// need it in BackgroundWorker_ProgressChanged.
}
// You can set e.Result = to some result;
}
void BackgroundWorker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
// This method runs in the UI thread and receives messages from the backgroud thread.
// Report progress using the value e.ProgressPercentage and e.UserState
}
void BackgroundWorker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
// This method runs in the UI thread.
// Work is finished! You can display the work done by using e.Result
}
UPDATE
This BackgroundWorker has to be in the presenter of cause. The idea of patterns like MVP, MVC or MVVM is to remove as much code from the view as possible. The view would only have code very specific to the view itself, like creating the view or drawing in the Paint event handler and so on. Another kind of code in the view is the code necessary to communicate with the presenter or controller. The presenting logic, however, has to be in the presenter.
You would use the BackgroundWorker_ProgressChanged method that runs in the UI thread to send changes to the view. Either by calling public methods of the view or by setting public properties of the view or by exposing public properties the view can attach to by binding its properties or the properties of its controls to it. (This is borrowed from the MVVM pattern.) The presenter must implement INotifyPropertyChanged in order to notify the view that a property has changed, if you decide to bind the view to properties of the presenter.
Note: Another thread than the UI thread is not allowed to interact with the view directly (an exception is thrown if you try to do so). Therefore the BackgroundWorker_DoWork cannot interact with the view directly and therefore calls ReportProgress, which in turn runs BackgroundWorker_ProgressChanged in the UI thread.
You can place the BackGroundWorker in the presenter and add a method to the view to show the progress.
Something like this:
//Add a method to your view interface to show progress if you need it.
public interface IView
{
void ShowProgress(int progressPercentage);
}
//Implement method in the view.
public class MyView : Form, IView
{
public MyView()
{
//Assume you have added a ProgressBar to the form in designer.
InitializeComponent();
}
public void ShowProgress(int progressPercentage)
{
//Make it thread safe.
if (progressBar1.InvokeRequired)
progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Value = progressPercentage; }));
else
progressBar1.Value = progressPercentage;
}
}
// In your presenter class create a BackgroundWorker and handle it's do work event and put your time consuming method there.
public class MyPresenter
{
private BackgroundWorker _bw;
public MyPresenter()
{
_bw = new BackgroundWorker();
_bw.WorkerReportsProgress = true;
_bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
}
private void _bw_DoWork(object sender, DoWorkEventArgs e)
{
//Time consuming operation
while (!finished)
{
//Do the job
_bw.ReportProgress(jobProgressPercentage);
}
}
public void StartTimeConsumingJob()
{
_bw.RunWorkerAsync();
}
}
Don't forget to Dispose the BackgroundWorker when you're finished.
with your input I've managed to work this out. Please comment any flaws you may find with this approach:
* View interface *
public interface IView
{
void ShowProgress( int progressPercentage);
}
* View (a form) *
public partial class Form1 : Form, IView
{
MyPresenter p ;
public Form1()
{
InitializeComponent();
p = new MyPresenter(this);
}
private void button1_Click(object sender, EventArgs e)
{
if (p.IsBusy())
{
return;
}
p.StartTimeConsumingJob();
}
public void ShowProgress(int progressPercentage)
{
if (progressBar1.InvokeRequired)
progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Value = progressPercentage; }));
else
progressBar1.Value = progressPercentage;
}
private void button2_Click(object sender, EventArgs e)
{
p.Cancel();
}
}
* Presenter *
public class MyPresenter
{
private BackgroundWorker _bw;
private IView _view;
public MyPresenter(IView Iview)
{
_view = Iview;
_bw = new BackgroundWorker();
_bw.WorkerReportsProgress = true;
_bw.WorkerSupportsCancellation = true;
_bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
_bw.ProgressChanged += new ProgressChangedEventHandler(_bw_ProgressChanged);
_bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_Completed);
}
public void StartTimeConsumingJob()
{
_bw.RunWorkerAsync();
}
private void _bw_DoWork(object sender, DoWorkEventArgs e)
{
//Time consuming operation Do the job
Thread.Sleep(1000);
_bw.ReportProgress(50);
Thread.Sleep(2000);
if(_bw.CancellationPending)
{
e.Result = false;
}
}
public bool IsBusy()
{
return _bw.IsBusy;
}
public void Cancel()
{
_bw.CancelAsync();
}
private void _bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
_view.ShowProgress(e.ProgressPercentage);
}
private void _bw_Completed(object sender, RunWorkerCompletedEventArgs e)
{
if((bool)e.Result)
_view.ShowProgress(100);
else
_view.ShowProgress(0);
_bw.Dispose();
}
}
Any way to make this working code simpler ie the delegate { }?
public partial class Form1 : Form
{
private CodeDevice codeDevice;
public Form1()
{
InitializeComponent();
codeDevice = new CodeDevice();
//subscribe to CodeDevice.ConnectionSuccessEvent and call Form1.SetupDeviceForConnectionSuccessSate when it fires
codeDevice.ConnectionSuccessEvent += new EventHandler(SetupDeviceForConnectionSuccessState);
}
private void SetupDeviceForConnectionSuccessState(object sender, EventArgs args)
{
MessageBox.Show("It worked");
}
private void button1_Click(object sender, EventArgs e)
{
codeDevice.test();
}
}
public class CodeDevice
{
public event EventHandler ConnectionSuccessEvent = delegate { };
public void ConnectionSuccess()
{
ConnectionSuccessEvent(this, new EventArgs());
}
public void test()
{
System.Threading.Thread.Sleep(1000);
ConnectionSuccess();
}
}
WinForm event subscription to another class
How to subscribe to other class' events in c#?
If don't think you could simplyfy:
public event EventHandler ConnectionSuccessEvent = delegate { }
even in c#3 + you could only do
public event EventHandler ConnectionSuccessEvent = () => { }
However you could simplify
codeDevice.ConnectionSuccessEvent += new EventHandler(SetupDeviceForConnectionSuccessState);
to
codeDevice.ConnectionSuccessEvent += SetupDeviceForConnectionSuccessState;