I would like to use MVP Design pattern for a WinForm App but i'm facing the problem of calling a View Update from another thread.
Here's my code
MODEL
public class Model : IModel
{
public string Status { get; set; }
public async void LongOperation(IHomeView View)
{
for (int i = 0; i < 1000; i++)
{
View.StatusListView = i.ToString();
}
}
}
PRESENTER
public class HomePresenter
{
IHomeView _IView;
IModel _IModel;
Model _Model = new Model();
public HomePresenter(IHomeView IView)
{
_IView = IView;
}
public async void LaunchLongOperation()
{
await Task.Run(() => _Model.LongOperation(_IView));
}
}
INTERFACE VIEW-PRESENTER
public interface IHomeView
{
string StatusListView { get; set; }
}
INTERFACE PRESENTER-MODEL
public interface IModel
{
string Status { get; set; }
}
FORM:
public partial class frmMain : Form, IHomeView
{
HomePresenter _Presenter;
public frmMain()
{
InitializeComponent();
_Presenter = new HomePresenter(this);
}
public string StatusListView
{
get
{
return lstActivityLog.Text;
}
set
{
lstActivityLog.Items.Add(value);
}
}
private void btnAvvia_Click(object sender, EventArgs e)
{
_Presenter.launchLongOperation();
}
}
i would like to update a list view in the Main form during the long operations of the Model class.
Which is the best way to do that?
Try this code without debugging, you'll be surprised about it works!
The quick and dirty way to make it work in debugging mode as well is to add Control.CheckForIllegalCrossThreadCalls = false; into the constructor of your form.
public partial class MainForm : Form, IHomeView
{
HomePresenter _Presenter;
public MainForm()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false; //<-- add this
_Presenter = new HomePresenter(this);
}
public string StatusListView
{
get
{
return lstActivityLog.Text;
}
set
{
lstActivityLog.Items.Add(value);
}
}
private void button1_Click(object sender, EventArgs e)
{
_Presenter.LaunchLongOperation();
}
}
Related
I have the following code, I would like to call the function RefreshProcess(SaveEventTriggerModelArgs obj) from MainWindow_Loaded.
However the problem I am running into due to lack of knowledge working with window apps I calling this method inside.
It will not let me because of the arguments SaveEventTriggerModelArgs obj and if I add those into RefreshProcess, they are different from void MainWindow_Loaded(object sender, RoutedEventArgs e). How to do it?
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded+= Window_Loaded;
}
private void RefreshProcess(SaveEventTriggerModelArgs obj)
{
var rect = new Rect();
Dispatcher.Invoke(() =>
{
obj.CurrentEventTriggerModel.ProcessInfo = new ProcessInfo()
{
ProcessName = "Nox" != null ? $"Nox" : "",
Position = rect,
};
});
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
public class SaveEventTriggerModelArgs : INotifyEventArgs
{
public Model CurrentEventTriggerModel { get; set; }
}
public class MousePointEventArgs : INotifyEventArgs
{
public ViewModel MousePointViewMode { get; set; }
}
public class ViewModel
{
}
public class Model
{
public ProcessInfo ProcessInfo { get;set;}
}
public class ProcessInfo
{
public string ProcessName { get;set;}
public Rect Position { get;set;}
}
I am using ReactiveUI in a Xamarin.Forms project but when I press a button I get the error: "Only the original thread that created a view hierarchy can touch its views. '"
Here is my code.
App.xaml.cs
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
AppShell.xaml.cs
public partial class AppShell : Shell
{
Random rand = new Random();
Dictionary<string, Type> routes = new Dictionary<string, Type>();
public Dictionary<string, Type> Routes { get { return routes; } }
public AppShell()
{
InitializeComponent();
RegisterRoutes();
BindingContext = this;
}
void RegisterRoutes()
{
routes.Add("monkeydetails", typeof(HomeView));
foreach (var item in routes)
{
Routing.RegisterRoute(item.Key, item.Value);
}
}
void OnNavigating(object sender, ShellNavigatingEventArgs e)
{
// Cancel any back navigation
//if (e.Source == ShellNavigationSource.Pop)
//{
// e.Cancel();
//}
}
void OnNavigated(object sender, ShellNavigatedEventArgs e)
{
}
}
By default you go to the HomeView view
HomeView.xaml
<Button Text="Pulsar" x:Name="Boton"></Button>
HomeView.xaml.cs
public partial class HomeView : ReactiveContentPage<HomeViewModel>
{
protected CompositeDisposable ControlBindings { get; } = new CompositeDisposable();
public ReactiveCommand<Unit, Unit> Navigate { get; private set; }
public HomeView()
{
InitializeComponent();
this.ViewModel = new HomeViewModel();
this.BindCommand(ViewModel, vm => vm.Navigate, view => view.Boton);
}
}
HomeViewModel.cs
public class HomeViewModel : ViewModelBase, IRoutableViewModel
{
int prueb = 0;
public HomeViewModel()
{
Navigate = ReactiveCommand.CreateFromTask(async() =>
{
await hola();
});
}
public async Task hola()
{
}
public string prueba()
{
return prueb.ToString();
}
public IObservable<string> NumberStream { get; }
public string UrlPathSegment => "Number Stream Page";
public IScreen HostScreen { get; }
public override string Id => "Pass Parameter";
public ReactiveCommand<Unit, Unit> Navigate { get; private set; }
}
I can't understand why the error appears when I press the button
I am really new to mvvm and wpf in c# and got stuck at some very basic stuff.In this example I am using Fody.PropertyChanged. I have a basic viewmodel that holds a string called Test which is binded to a textblock.
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
public string Test { get; set; }
}
Then,in a separate file and class called Data,I have a simple function that increments an int and converts it to a string.
public class Data
{
public static int i = 0;
public static string IncTest { get; set; }
public static void Inc()
{
i++;
IncTest = i.ToString();
}
}
How do I update the Test variable inside the viewmodel when calling the Inc() function? For example, when clicking a button
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new Model();
Data.Inc();
}
private void Increment_Click(object sender, RoutedEventArgs e)
{
Data.Inc();
}
In MVVM, the model does not update the view model, its actually opposite, The view model updates the model properties.
Here is an example.
MODEL:
public class Model
{
public string Test
{
get;
set;
}
}
View Model:
public class ViewModel : INotifyPropertyChanged
{
private Model _model;
public string Test
{
get
{
return _model.Test;
}
set
{
if(string.Equals(value, _model.Test, StringComparison.CurrentCulture))
{
return;
}
_model.Test = value;
OnPropertyChanged();
}
}
public ViewModel(Model model)
{
_model = model;
}
}
Your views will bind to your view models.
UPDATE: In regards to your question
public class SomeClass
{
public static void Main(string [] args)
{
Model model = new Model();
ViewModel viewModel = new ViewModel(model);
//Now setting the viewmodel.Test will update the model property
viewModel.Test = "This is a test";
}
}
I have a child and a parent class in C#. I want to point the Calc delegate to one of several methods. this is determined each time the Reset() method is called.
Below is a working example.
However, I want this functionality of pointing the delegate to reside on the Parent class. Since the parent does not contain the methods, I don't know how to do it...
class Program
{
static void Main(string[] args)
{
Model Model = new Model();
Model.Env1 = true;
Child Ch = new Child(Model);
Ch.Reset();
Ch.Calc(); Ch.Calc(); Ch.Calc();
Console.WriteLine();
Model.Env1 = false;
Ch.Reset();
Ch.Calc(); Ch.Calc(); Ch.Calc();
Console.ReadLine();
}
}
public class Parent
{
public Model Model { get; set; }
public Parent(Model model)
{
Model = model;
}
public delegate void StateHandler();
public StateHandler Calc;
}
public class Model
{
public bool Env1 { get; set; }
}
public class Child : Parent, IChild
{
public Child (Model model)
: base (model)
{
}
public void Reset()
{
if (Model.Env1)
Calc = CalcHeavy;
else
Calc = CalcLight;
}
public void CalcHeavy()
{
Console.WriteLine("CalcHeavy is active");
}
public void CalcLight()
{
Console.WriteLine("CalcLight is active");
}
}
public interface IChild
{
void CalcHeavy();
void CalcLight();
}
One way to do that would be to have the Child class inject its methods into the Parent at construction time. That way, the Parent class makes the choice, but the Child class defines the functionality:
public class Parent
{
private readonly Action _env1Method;
private readonly Action _notEnv1Method;
private readonly Model _model;
public Parent(Model model,
Action env1Method,
Action notEnv1Method)
{
_model = model;
_env1Method = env1Method;
_notEnv1Method = notEnv1Method;
Reset();
}
public Action Calc { get; private set; }
public void Reset()
{
Calc = _model.Env1 ? _env1Method : _notEnv1Method;
}
}
public class Model
{
public bool Env1 { get; set; }
}
public class Child : Parent
{
public Child (Model model) : base (model, CalcHeavy, CalcLight) {}
private static void CalcHeavy()
{
Console.WriteLine("CalcHeavy is active");
}
private static void CalcLight()
{
Console.WriteLine("CalcLight is active");
}
}
Also, I'd get rid of StateHandler and just use Action instead. Don't create a new delegate when a standard one exists already.
While trying to get the answer I liked to work, I managed to solve it in a different way by making the Parent abstract, and it works.
class Program
{
static void Main(string[] args)
{
Model Model = new Model();
Model.Env1 = true;
Child Ch = new Child(Model);
Ch.Calc(); Ch.Calc(); Ch.Calc();
Console.WriteLine();
Model.Env1 = false;
Ch = new Child(Model);
Ch.Calc(); Ch.Calc(); Ch.Calc();
Console.ReadLine();
}
}
public abstract class Parent
{
public Model Model { get; set; }
public Parent(Model model)
{
Model = model;
if (Model.Env1)
Calc = CalcHeavy;
else
Calc = CalcLight;
}
public Action Calc;
public abstract void CalcHeavy();
public abstract void CalcLight();
}
public class Model
{
public bool Env1 { get; set; }
}
public class Child : Parent
{
public Child(Model model) : base(model) { }
public override void CalcHeavy()
{
Console.WriteLine("CalcHeavy is active");
}
public override void CalcLight()
{
Console.WriteLine("CalcLight is active");
}
}
One way is to move the "Reset" method to the parent class and do something like this:
public void Reset()
{
var child = this as IChild;
if (child != null && Model.Env1)
Calc = child.CalcHeavy;
else
Calc = child.CalcLight;
}
I have a background process that i want to regularly maintain the state of gps location. I am not clear on how to invoke a delegate on the main thread in the ui layer when the threaded method is in another class. Here is sample code. My form launches the thread on load:
public partial class MainScreen : Form
{
.
. // form stuff
.
private void MainScreen_Load(object sender, EventArgs e)
{
var gpsStatusManager = new GpsStatusManager();
Thread t = new Thread(gpsStatusManager.UpdateLocation);
t.IsBackground = true;
t.Start();
}
delegate void GpsDataParameterDelegate(GpsStatus value);
public void UpdateGpsStatus(GpsStatus value)
{
if (InvokeRequired)
{
// We're not in the UI thread, so we need to call BeginInvoke
BeginInvoke(new GpsDataParameterDelegate(UpdateGpsStatus), new object[] { value });
return;
}
// Must be on the UI thread if we've got this far
gpsStatus.SetGpsStatus(value);
}
}
I have a domain object class for the gps information:
public class GpsStatus
{
public void SetGpsStatus(GpsStatus gpsStatus)
{
Latitude = gpsStatus.Latitude;
Longitude = gpsStatus.Longitude;
CurrentDateTime = gpsStatus.CurrentDateTime;
NumberOfSatellites = gpsStatus.NumberOfSatellites;
TotalNumberSatellites = gpsStatus.TotalNumberSatellites;
}
public float Latitude { get; private set; }
public float Longitude { get; private set; }
public DateTime CurrentDateTime { get; private set; }
public int NumberOfSatellites { get; private set; }
public int TotalNumberSatellites { get; private set; }
}
Then, my manager class where i update status in the secondary thread:
public class GpsStatusManager
{
private GpsStatus _gpsStatus;
public void UpdateLocationx()
{
while (UpdateGpsData())
{
Thread.Sleep(2000);
}
}
private bool UpdateGpsData()
{
SError error;
SGpsPosition gpsPosition;
try
{
if (CApplicationAPI.GetActualGpsPosition(out error, out gpsPosition, true, 0) != 1)
return false;
}
catch (Exception)
{
return false;
}
var numberOfSatellites = gpsPosition.Satellites;
var totalSatellites = gpsPosition.satellitesInfo;
var datetime = gpsPosition.Time;
var lat = gpsPosition.Latitude;
var lon = gpsPosition.Longitude;
_gpsStatus.SetGpsStatus(lat, lon, datetime, numberOfSatellites, totalSatellites);
//How do I invoke the delegate to send the _gpsStatus data to my main thread?
return true;
}
}
Thanks for any assistance.
Here's one way to do it, just off the top of my head:
public class GpsStatusEventArgs : EventArgs
{
public GpsStatus Status { get; private set; }
public GpsStatusEventArgs(GpsStatus status)
{
Status = status;
}
}
public class GpsStatusManager
{
...
public event EventHandler<GpsStatusEventArgs> GpsStatusUpdated;
private void OnGpsStatusUpdated(GpsStatus gpsStatus)
{
EventHandler<GpsStatusEventArgs> temp = GpsStatusUpdated;
if (temp != null)
temp.Invoke(this, new GpsStatusEventArgs(gpsStatus));
}
}
public partial class MainScreen : Form
{
...
private void MainScreen_Load(object sender, EventArgs e)
{
var gpsStatusManager = new GpsStatusManager();
gpsStatusManager.GpsStatusUpdated += new EventHandler<GpsStatusEventArgs>(GpsStatusManager_GpsStatusUpdated);
...
}
private void GpsStatusManager_GpsStatusUpdated(object sender, GpsStatusEventArgs e)
{
UpdateGpsStatus(e.Status);
}
...
}
Then add this to the bottom of UpdateGpsData:
OnGpsStatusUpdated(_gpsStatus);
You should use the SynchronizationContext class.
In the UI thread (in any class), set a field (perhaps static) to SynchronizationContext.Current.
You can then call Send or Post on the saved instance to execute code on the UI thread.
Here is another approach using the ISynchronizeInvoke interface. This is the same pattern the System.Timers.Timer class uses to raise the Elapsed event.
public class GpsStatusManager
{
public ISynchronizeInvoke SynchronizingObject { get; set; }
public event EventHandler Update;
public void UpdateGpsData()
{
// Code omitted for brevity.
OnUpdate(_gpsStatus);
return true;
}
private OnUpdate(GpsStatus status)
{
if (SynchronizingObject != null && SynchronizingObject.IsInvokeRequired)
{
ThreadStart ts = () => { OnUpdate(status); };
SynchronizingObject.Invoke(ts, null);
}
else
{
if (Update != null)
{
Update(this, status);
}
}
}
public class UpdateEventArgs : EventArgs
{
public GpsStatus Status { get; set; }
}
}